From 83b52e3a977bab3759e0c6d4fb6d2e30d1aee245 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Wed, 15 May 2019 09:00:01 +0000 Subject: [PATCH] [FIX] account: faster reconciliation This reverts commit dbaef7de1743c19b21dfe8d89e2ae105362aaf7e since it causes issues (unbalanced moves). Temporary revert so we have time to analyze the issue. opw-1997794 closes odoo/odoo#33380 Signed-off-by: Nicolas Martinelli (nim) --- .../account/models/account_bank_statement.py | 123 +++++------------- addons/account/models/account_move.py | 29 ++--- 2 files changed, 45 insertions(+), 107 deletions(-) diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 39ede2913fa..58c9825164e 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -435,7 +435,6 @@ class AccountBankStatementLine(models.Model): """ Prepare the dict of values to balance the move. :param recordset move: the account.move to link the move line - :param dict move: a dict of vals of a account.move which will be created later :param float amount: the amount of transaction that wasn't already reconciled """ company_currency = self.journal_id.company_id.currency_id @@ -443,23 +442,19 @@ class AccountBankStatementLine(models.Model): st_line_currency = self.currency_id or statement_currency amount_currency = False st_line_currency_rate = self.currency_id and (self.amount_currency / self.amount) or False - if isinstance(move, dict): - amount_sum = sum(x[2].get('amount_currency', 0) for x in move['line_ids']) - else: - amount_sum = sum(x.amount_currency for x in move.line_ids) # We have several use case here to compare the currency and amount currency of counterpart line to balance the move: if st_line_currency != company_currency and st_line_currency == statement_currency: # company in currency A, statement in currency B and transaction in currency B # counterpart line must have currency B and correct amount is inverse of already existing lines - amount_currency = -amount_sum + amount_currency = -sum([x.amount_currency for x in move.line_ids]) elif st_line_currency != company_currency and statement_currency == company_currency: # company in currency A, statement in currency A and transaction in currency B # counterpart line must have currency B and correct amount is inverse of already existing lines - amount_currency = -amount_sum + amount_currency = -sum([x.amount_currency for x in move.line_ids]) elif st_line_currency != company_currency and st_line_currency != statement_currency: # company in currency A, statement in currency B and transaction in currency C # counterpart line must have currency B and use rate between B and C to compute correct amount - amount_currency = -amount_sum/st_line_currency_rate + amount_currency = -sum([x.amount_currency for x in move.line_ids])/st_line_currency_rate elif st_line_currency == company_currency and statement_currency != company_currency: # company in currency A, statement in currency B and transaction in currency A # counterpart line must have currency B and amount is computed using the rate between A and B @@ -468,8 +463,9 @@ class AccountBankStatementLine(models.Model): # last case is company in currency A, statement in currency A and transaction in currency A # and in this case counterpart line does not need any second currency nor amount_currency - aml_dict = { + return { 'name': self.name, + 'move_id': move.id, 'partner_id': self.partner_id and self.partner_id.id or False, 'account_id': amount >= 0 \ and self.statement_id.journal_id.default_credit_account_id.id \ @@ -480,71 +476,22 @@ class AccountBankStatementLine(models.Model): 'currency_id': statement_currency != company_currency and statement_currency.id or (st_line_currency != company_currency and st_line_currency.id or False), 'amount_currency': amount_currency, } - if isinstance(move, self.env['account.move'].__class__): - aml_dict['move_id'] = move.id - return aml_dict - @api.multi def fast_counterpart_creation(self): """This function is called when confirming a bank statement and will allow to automatically process lines without going in the bank reconciliation widget. By setting an account_id on bank statement lines, it will create a journal entry using that account to counterpart the bank account """ - payment_list = [] - move_list = [] - account_type_receivable = self.env.ref('account.data_account_type_receivable') - already_done_stmt_line_ids = [a['statement_line_id'][0] for a in self.env['account.move.line'].read_group([('statement_line_id', 'in', self.ids)], ['statement_line_id'], ['statement_line_id'])] - managed_st_line = [] for st_line in self: # Technical functionality to automatically reconcile by creating a new move line - if st_line.account_id and not st_line.id in already_done_stmt_line_ids: - managed_st_line.append(st_line.id) - # Create payment vals - total = st_line.amount - payment_methods = (total > 0) and st_line.journal_id.inbound_payment_method_ids or st_line.journal_id.outbound_payment_method_ids - currency = st_line.journal_id.currency_id or st_line.company_id.currency_id - partner_type = 'customer' if st_line.account_id.user_type_id == account_type_receivable else 'supplier' - payment_list.append({ - 'payment_method_id': payment_methods and payment_methods[0].id or False, - 'payment_type': total > 0 and 'inbound' or 'outbound', - 'partner_id': st_line.partner_id.id, - 'partner_type': partner_type, - 'journal_id': st_line.statement_id.journal_id.id, - 'payment_date': st_line.date, - 'state': 'reconciled', - 'currency_id': currency.id, - 'amount': abs(total), - 'communication': st_line._get_communication(payment_methods[0] if payment_methods else False), - 'name': st_line.statement_id.name or _("Bank Statement %s") % st_line.date, - }) - - # Create move and move line vals - move_vals = st_line._prepare_reconciliation_move(st_line.statement_id.name) - aml_dict = { + if st_line.account_id and not st_line.journal_entry_ids.ids: + vals = { 'name': st_line.name, 'debit': st_line.amount < 0 and -st_line.amount or 0.0, 'credit': st_line.amount > 0 and st_line.amount or 0.0, 'account_id': st_line.account_id.id, - 'partner_id': st_line.partner_id.id, - 'statement_line_id': st_line.id, } - st_line._prepare_move_line_for_currency(aml_dict, st_line.date or fields.Date.context_today()) - move_vals['line_ids'] = [(0, 0, aml_dict)] - balance_line = self._prepare_reconciliation_move_line(move_vals, st_line.amount) - move_vals['line_ids'].append((0, 0, balance_line)) - move_list.append(move_vals) - - # Creates - payment_ids = self.env['account.payment'].create(payment_list) - for payment_id, move_vals in pycompat.izip(payment_ids, move_list): - for line in move_vals['line_ids']: - line[2]['payment_id'] = payment_id.id - move_ids = self.env['account.move'].create(move_list) - move_ids.post() - - for move, st_line, payment in pycompat.izip(move_ids, self.browse(managed_st_line), payment_ids): - st_line.write({'move_name': move.name}) - payment.write({'payment_reference': move.name}) + st_line.process_reconciliation(new_aml_dicts=[vals]) def _get_communication(self, payment_method_id): return self.name or '' @@ -634,6 +581,8 @@ class AccountBankStatementLine(models.Model): # Create move line(s). Either matching an existing journal entry (eg. invoice), in which # case we reconcile the existing and the new move lines together, or being a write-off. if counterpart_aml_dicts or new_aml_dicts: + st_line_currency = self.currency_id or statement_currency + st_line_currency_rate = self.currency_id and (self.amount_currency / self.amount) or False # Create the move self.sequence = self.statement_id.line_ids.ids.index(self.id) + 1 @@ -672,12 +621,32 @@ class AccountBankStatementLine(models.Model): # Complete dicts to create both counterpart move lines and write-offs to_create = (counterpart_aml_dicts + new_aml_dicts) + company = self.company_id date = self.date or fields.Date.today() for aml_dict in to_create: aml_dict['move_id'] = move.id aml_dict['partner_id'] = self.partner_id.id aml_dict['statement_line_id'] = self.id - self._prepare_move_line_for_currency(aml_dict, date) + if st_line_currency.id != company_currency.id: + aml_dict['amount_currency'] = aml_dict['debit'] - aml_dict['credit'] + aml_dict['currency_id'] = st_line_currency.id + if self.currency_id and statement_currency.id == company_currency.id and st_line_currency_rate: + # Statement is in company currency but the transaction is in foreign currency + aml_dict['debit'] = company_currency.round(aml_dict['debit'] / st_line_currency_rate) + aml_dict['credit'] = company_currency.round(aml_dict['credit'] / st_line_currency_rate) + elif self.currency_id and st_line_currency_rate: + # Statement is in foreign currency and the transaction is in another one + aml_dict['debit'] = statement_currency._convert(aml_dict['debit'] / st_line_currency_rate, company_currency, company, date) + aml_dict['credit'] = statement_currency._convert(aml_dict['credit'] / st_line_currency_rate, company_currency, company, date) + else: + # Statement is in foreign currency and no extra currency is given for the transaction + aml_dict['debit'] = st_line_currency._convert(aml_dict['debit'], company_currency, company, date) + aml_dict['credit'] = st_line_currency._convert(aml_dict['credit'], company_currency, company, date) + elif statement_currency.id != company_currency.id: + # Statement is in foreign currency but the transaction is in company currency + prorata_factor = (aml_dict['debit'] - aml_dict['credit']) / self.amount_currency + aml_dict['amount_currency'] = prorata_factor * self.amount + aml_dict['currency_id'] = statement_currency.id # Create write-offs for aml_dict in new_aml_dicts: @@ -727,36 +696,6 @@ class AccountBankStatementLine(models.Model): counterpart_moves.assert_balanced() return counterpart_moves - @api.multi - def _prepare_move_line_for_currency(self, aml_dict, date): - self.ensure_one() - company_currency = self.journal_id.company_id.currency_id - statement_currency = self.journal_id.currency_id or company_currency - st_line_currency = self.currency_id or statement_currency - st_line_currency_rate = self.currency_id and (self.amount_currency / self.amount) or False - company = self.company_id - - if st_line_currency.id != company_currency.id: - aml_dict['amount_currency'] = aml_dict['debit'] - aml_dict['credit'] - aml_dict['currency_id'] = st_line_currency.id - if self.currency_id and statement_currency.id == company_currency.id and st_line_currency_rate: - # Statement is in company currency but the transaction is in foreign currency - aml_dict['debit'] = company_currency.round(aml_dict['debit'] / st_line_currency_rate) - aml_dict['credit'] = company_currency.round(aml_dict['credit'] / st_line_currency_rate) - elif self.currency_id and st_line_currency_rate: - # Statement is in foreign currency and the transaction is in another one - aml_dict['debit'] = statement_currency._convert(aml_dict['debit'] / st_line_currency_rate, company_currency, company, date) - aml_dict['credit'] = statement_currency._convert(aml_dict['credit'] / st_line_currency_rate, company_currency, company, date) - else: - # Statement is in foreign currency and no extra currency is given for the transaction - aml_dict['debit'] = st_line_currency._convert(aml_dict['debit'], company_currency, company, date) - aml_dict['credit'] = st_line_currency._convert(aml_dict['credit'], company_currency, company, date) - elif statement_currency.id != company_currency.id: - # Statement is in foreign currency but the transaction is in company currency - prorata_factor = (aml_dict['debit'] - aml_dict['credit']) / self.amount_currency - aml_dict['amount_currency'] = prorata_factor * self.amount - aml_dict['currency_id'] = statement_currency.id - def _check_invoice_state(self, invoice): if invoice.state == 'in_payment' and all([payment.state == 'reconciled' for payment in invoice.mapped('payment_move_line_ids.payment_id')]): invoice.write({'state': 'paid'}) diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 62ee5dfc7d6..6d11d14aa8b 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -54,14 +54,12 @@ class AccountMove(models.Model): if line.account_id.user_type_id.type in ('receivable', 'payable'): amount = abs(line.debit - line.credit) total_amount += amount + for partial_line in (line.matched_debit_ids + line.matched_credit_ids): + total_reconciled += partial_line.amount precision_currency = move.currency_id or move.company_id.currency_id if float_is_zero(total_amount, precision_rounding=precision_currency.rounding): move.matched_percentage = 1.0 else: - for line in move.line_ids: - if line.account_id.user_type_id.type in ('receivable', 'payable'): - for partial_line in (line.matched_debit_ids + line.matched_credit_ids): - total_reconciled += partial_line.amount move.matched_percentage = total_reconciled / total_amount @api.one @@ -742,21 +740,22 @@ class AccountMoveLine(models.Model): In case of full reconciliation, all moves belonging to the reconciliation will belong to the same account_full_reconcile object. """ # Get first all aml involved - todo = self.env['account.partial.reconcile'].search_read(['|', ('debit_move_id', 'in', self.ids), ('credit_move_id', 'in', self.ids)], ['debit_move_id', 'credit_move_id']) - amls = set(self.ids) + part_recs = self.env['account.partial.reconcile'].search(['|', ('debit_move_id', 'in', self.ids), ('credit_move_id', 'in', self.ids)]) + amls = self + todo = set(part_recs) seen = set() while todo: - aml_ids = [rec['debit_move_id'][0] for rec in todo if rec['debit_move_id']] + [rec['credit_move_id'][0] for rec in todo if rec['credit_move_id']] - amls |= set(aml_ids) - seen |= set([rec['id'] for rec in todo]) - todo = self.env['account.partial.reconcile'].search_read(['&', '|', ('credit_move_id', 'in', aml_ids), ('debit_move_id', 'in', aml_ids), '!', ('id', 'in', list(seen))], ['debit_move_id', 'credit_move_id']) - - partial_rec_ids = list(seen) + partial_rec = todo.pop() + seen.add(partial_rec) + for aml in [partial_rec.debit_move_id, partial_rec.credit_move_id]: + if aml not in amls: + amls += aml + for x in aml.matched_debit_ids | aml.matched_credit_ids: + if x not in seen: + todo.add(x) + partial_rec_ids = [x.id for x in seen] if not amls: return - else: - amls = self.browse(list(amls)) - # If we have multiple currency, we can only base ourselve on debit-credit to see if it is fully reconciled currency = set([a.currency_id for a in amls if a.currency_id.id != False]) multiple_currency = False