diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index c07bb575ced..dac9ba4a48c 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -391,6 +391,9 @@ class AccountMove(models.Model): date = date or fields.Date.today() reversed_moves = self.env['account.move'] for ac_move in self: + #unreconcile all lines reversed + aml = ac_move.line_ids.filtered(lambda x: x.account_id.reconcile or x.account_id.internal_type == 'liquidity') + aml.remove_move_reconcile() reversed_move = ac_move._reverse_move(date=date, journal_id=journal_id, auto=auto) @@ -464,7 +467,7 @@ class AccountMoveLine(models.Model): for unreconciled lines, and something in-between for partially reconciled lines. """ for line in self: - if not line.account_id.reconcile: + if not line.account_id.reconcile and line.account_id.internal_type != 'liquidity': line.reconciled = False line.amount_residual = 0 line.amount_residual_currency = 0 @@ -607,7 +610,7 @@ class AccountMoveLine(models.Model): help="This field is used for payable and receivable journal entries. You can put the limit date for the payment of this line.") date = fields.Date(related='move_id.date', string='Date', index=True, store=True, copy=False) # related is required analytic_line_ids = fields.One2many('account.analytic.line', 'move_id', string='Analytic lines', oldname="analytic_lines") - tax_ids = fields.Many2many('account.tax', string='Taxes') + tax_ids = fields.Many2many('account.tax', string='Taxes', domain=['|', ('active', '=', False), ('active', '=', True)]) tax_line_id = fields.Many2one('account.tax', string='Originator tax', ondelete='restrict') analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', index=True) analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags') @@ -1553,7 +1556,6 @@ class AccountPartialReconcile(models.Model): """ When removing a partial reconciliation, also unlink its full reconciliation if it exists """ full_to_unlink = self.env['account.full.reconcile'] for rec in self: - #without the deleted partial reconciliations, the full reconciliation won't be full anymore if rec.full_reconcile_id: full_to_unlink |= rec.full_reconcile_id #reverse the tax basis move created at the reconciliation time diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py index cf104b14073..b95875eb954 100644 --- a/addons/account/tests/test_reconciliation.py +++ b/addons/account/tests/test_reconciliation.py @@ -817,6 +817,74 @@ class TestReconciliation(AccountingTestCase): self.assertEqual(positive_line[0]['amount'], 50.0, 'The amount of the amls should be 50') self.assertEqual(negative_line[0]['amount'], -50.0, 'The amount of the amls should be -50') + def test_revert_payment_and_reconcile_exchange(self): + + # A reversal of a reconciled payment which created a currency exchange entry, should create reversal moves + # which move lines should be reconciled two by two with the original move's lines + + def _determine_debit_credit_line(move): + line_ids_reconciliable = move.line_ids.filtered(lambda l: l.account_id.reconcile or l.account_id.internal_type == 'liquidity') + return line_ids_reconciliable.filtered(lambda l: l.debit), line_ids_reconciliable.filtered(lambda l: l.credit) + + def _move_revert_test_pair(move, revert): + self.assertTrue(move.line_ids) + self.assertTrue(revert.line_ids) + + move_lines = _determine_debit_credit_line(move) + revert_lines = _determine_debit_credit_line(revert) + + # in the case of the exchange entry, only one pair of lines will be found + if move_lines[0] and revert_lines[1]: + self.assertTrue(move_lines[0].full_reconcile_id.exists()) + self.assertEqual(move_lines[0].full_reconcile_id.id, revert_lines[1].full_reconcile_id.id) + + if move_lines[1] and revert_lines[0]: + self.assertTrue(move_lines[1].full_reconcile_id.exists()) + self.assertEqual(move_lines[1].full_reconcile_id.id, revert_lines[0].full_reconcile_id.id) + + self.env['res.currency.rate'].create({ + 'name': time.strftime('%Y') + '-07-01', + 'rate': 1.0, + 'currency_id': self.currency_usd_id, + 'company_id': self.env.ref('base.main_company').id + }) + self.env['res.currency.rate'].create({ + 'name': time.strftime('%Y') + '-08-01', + 'rate': 0.5, + 'currency_id': self.currency_usd_id, + 'company_id': self.env.ref('base.main_company').id + }) + inv = self.create_invoice(invoice_amount=111, currency_id=self.currency_usd_id) + payment = self.env['account.payment'].create({ + 'payment_type': 'inbound', + 'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id, + 'partner_type': 'customer', + 'partner_id': self.partner_agrolait_id, + 'amount': 111, + 'currency_id': self.currency_usd_id, + 'journal_id': self.bank_journal_usd.id, + 'payment_date': time.strftime('%Y') + '-08-01', + }) + payment.post() + + credit_aml = payment.move_line_ids.filtered('credit') + inv.assign_outstanding_credit(credit_aml.id) + self.assertTrue(inv.state == 'paid', 'The invoice should be paid') + + exchange_reconcile = payment.move_line_ids.mapped('full_reconcile_id') + exchange_move = exchange_reconcile.exchange_move_id + payment_move = payment.move_line_ids[0].move_id + + reverted_payment_move = self.env['account.move'].browse(payment_move.reverse_moves(time.strftime('%Y') + '-08-01')) + + # After reversal of payment, the invoice should be open + self.assertTrue(inv.state == 'open', 'The invoice should be open again') + self.assertFalse(exchange_reconcile.exists()) + + reverted_exchange_move = self.env['account.move'].search([('journal_id', '=', exchange_move.journal_id.id), ('ref', 'ilike', exchange_move.name)], limit=1) + _move_revert_test_pair(payment_move, reverted_payment_move) + _move_revert_test_pair(exchange_move, reverted_exchange_move) + def test_partial_reconcile_currencies_02(self): #### # Day 1: Invoice Cust/001 to customer (expressed in USD) diff --git a/addons/account/views/account_view.xml b/addons/account/views/account_view.xml index b2ceee51f15..7072abcad6f 100644 --- a/addons/account/views/account_view.xml +++ b/addons/account/views/account_view.xml @@ -1381,7 +1381,7 @@ - + diff --git a/addons/auth_oauth/controllers/main.py b/addons/auth_oauth/controllers/main.py index e231cd0f6d0..3b300070929 100644 --- a/addons/auth_oauth/controllers/main.py +++ b/addons/auth_oauth/controllers/main.py @@ -131,6 +131,8 @@ class OAuthController(http.Controller): def signin(self, **kw): state = json.loads(kw['state']) dbname = state['d'] + if not http.db_filter([dbname]): + return BadRequest() provider = state['p'] context = state.get('c', {}) registry = registry_get(dbname) @@ -180,6 +182,8 @@ class OAuthController(http.Controller): dbname = db_monodb() if not dbname: return BadRequest() + if not http.db_filter([dbname]): + return BadRequest() registry = registry_get(dbname) with registry.cursor() as cr: diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py index 729a2ac8d2e..8deecf4d657 100644 --- a/addons/calendar/models/calendar.py +++ b/addons/calendar/models/calendar.py @@ -948,6 +948,14 @@ class Meeting(models.Model): self.start = self.start_datetime self.stop = fields.Datetime.to_string(start + timedelta(hours=self.duration)) + @api.onchange('start_date') + def _onchange_start_date(self): + self.start = self.start_date + + @api.onchange('stop_date') + def _onchange_stop_date(self): + self.stop = self.stop_date + #################################################### # Calendar Business, Reccurency, ... #################################################### diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index 49b0cab9b4b..9e6c4a83416 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -553,7 +553,8 @@ class Lead(models.Model): for field in fields: value = getattr(self, field.name, False) if field.ttype == 'selection': - value = dict(field.get_values(self.env)).get(value, value) + selections = self.fields_get()[field.name]['selection'] + value = next((v[1] for v in selections if v[0] == value), value) elif field.ttype == 'many2one': if value: value = value.sudo().name_get()[0][1] diff --git a/addons/mail/models/mail_mail.py b/addons/mail/models/mail_mail.py index d8abbd1f06f..458fbeeac50 100644 --- a/addons/mail/models/mail_mail.py +++ b/addons/mail/models/mail_mail.py @@ -103,16 +103,19 @@ class MailMail(models.Model): messages to send (by default all 'outgoing' messages are sent). """ - if not self.ids: - filters = ['&', - ('state', '=', 'outgoing'), - '|', - ('scheduled_date', '<', datetime.datetime.now()), - ('scheduled_date', '=', False)] - if 'filters' in self._context: - filters.extend(self._context['filters']) - # TODO: make limit configurable - ids = self.search(filters, limit=10000).ids + filters = ['&', + ('state', '=', 'outgoing'), + '|', + ('scheduled_date', '<', datetime.datetime.now()), + ('scheduled_date', '=', False)] + if 'filters' in self._context: + filters.extend(self._context['filters']) + # TODO: make limit configurable + filtered_ids = self.search(filters, limit=10000).ids + if not ids: + ids = filtered_ids + else: + ids = list(set(filtered_ids) & set(ids)) res = None try: # auto-commit except in testing mode diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index e33588f24a6..df27d62a71b 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -1886,7 +1886,7 @@ class MailThread(models.AbstractModel): if not attachment: attachment = fname_mapping.get(node.get('data-filename'), '') if attachment: - node.set('src', '/web/image/%s' % attachment.id) + node.set('src', '/web/image/%s?access_token=%s' % (attachment.id, attachment.access_token)) postprocessed = True if postprocessed: body = lxml.html.tostring(root, pretty_print=False, encoding='UTF-8') diff --git a/addons/mail/static/src/js/composer.js b/addons/mail/static/src/js/composer.js index f89c0382554..64cc48285e5 100644 --- a/addons/mail/static/src/js/composer.js +++ b/addons/mail/static/src/js/composer.js @@ -655,7 +655,7 @@ var BasicComposer = Widget.extend({ on_attachment_delete: function(event){ event.stopPropagation(); var self = this; - var attachment_id = $(event.target).data("id"); + var attachment_id = $(event.currentTarget).data("id"); if (attachment_id) { var attachments = []; _.each(this.get('attachment_ids'), function(attachment){ @@ -666,6 +666,7 @@ var BasicComposer = Widget.extend({ } }); this.set('attachment_ids', attachments); + this.$('input.o_input_file').val(''); } }, do_check_attachment_upload: function () { diff --git a/addons/mail/wizard/invite_view.xml b/addons/mail/wizard/invite_view.xml index f9f9cb30b23..d64e10f387d 100644 --- a/addons/mail/wizard/invite_view.xml +++ b/addons/mail/wizard/invite_view.xml @@ -20,7 +20,7 @@ invisible="not context.get('mail_invite_follower_channel_only')" options="{'no_create': True}"/> - +