From bce02f46d317f887ba62ed0df00d5659a85c223a Mon Sep 17 00:00:00 2001 From: Laurent Smet Date: Fri, 10 Feb 2023 10:25:26 +0000 Subject: [PATCH] [IMP] account: Improve perf of reconciliation Allow to perform multiple reconciliation at once in order to: - ...batch the creation of records as much as possible. Creating an account.partial.reconcile force the orm to search for amls in order to invalidate the reconciliation fields like amount_residual/amount_residual_currency. - ...reduce the number of flush inside the orm and batch the compute. Each call to reconcile is looking for the payment's state of invoices before/after the reconciliation. This step is costly because this will flush all the reconciliation data and then call the computes to get the fresh value of payment_state. - ...modify more easily the way the lines are matched together. Before this commit, the lines were split into two batches sorted by some criteria including the currency: debit and credit. Then, we were matching them together sequentially. Now, we make the same things except we do that first for each batch of amls sharing the same currency in order to reduce the number of cross-currencies reconciliation. - ...allow the orm to prefetch all the partials in the reconciliation chain all at once. The full reconcile needs to be creating on the full reconciliation graph starting on the current amls so we need to travel the matched_debit_ids/matched_credit_ids in order to find all the involved amls. Fetching all this data at once is also reducing the number of queries made by the orm. Let's take an example: Suppose 10 amls: a1, a2, ..., a10 Suppose 10 amls: b1, b2, ..., b10 You want to reconcile respectively a1 with b1, ... , a10 with b10. Before this commit, each reconciliation was done as follow: - Check the payment_state of invoice (on 2 amls) - Create a partial reconcile (single record) - Create a full reconcile (single record) - Compute the reconciliation data to compute payment_state (on 2 amls) All of that, 10 times sequentially. With the new '_reconcile_plan' method, we are able to give a list of recordset [a1 + b1, ..., a10 + b10]: - Check the payment_state of invoice (on 20 amls) - Create a partial reconcile (10 records) - Create a full reconcile (10 records) - Compute the reconciliation data to compute payment_state (on 20 amls) For a reconciliation using 2000 records (a1, ..., a1000 & b1, ..., b1000), the time to reconcile it was about +-34 seconds. Now, it's about +- 3 seconds. closes odoo/odoo#113680 Related: odoo/enterprise#37543 Signed-off-by: Brice Bartoletti (bib) --- addons/account/models/account_move_line.py | 871 ++++++--- .../models/account_partial_reconcile.py | 13 +- .../tests/test_account_move_date_algorithm.py | 14 +- .../account/tests/test_account_move_entry.py | 5 +- .../tests/test_account_move_reconcile.py | 1734 ++++++++++------- addons/account_edi/models/account_move.py | 22 +- .../tests/test_stockvaluation.py | 4 +- 7 files changed, 1608 insertions(+), 1055 deletions(-) diff --git a/addons/account/models/account_move_line.py b/addons/account/models/account_move_line.py index e6d5916007e..2a32ce77ea6 100644 --- a/addons/account/models/account_move_line.py +++ b/addons/account/models/account_move_line.py @@ -1609,52 +1609,51 @@ class AccountMoveLine(models.Model): # ------------------------------------------------------------------------- @api.model - def _prepare_reconciliation_single_partial(self, debit_vals, credit_vals): + def _prepare_reconciliation_single_partial(self, debit_values, credit_values): """ Prepare the values to create an account.partial.reconcile later when reconciling the dictionaries passed as parameters, each one representing an account.move.line. - :param debit_vals: The values of account.move.line to consider for a debit line. - :param credit_vals: The values of account.move.line to consider for a credit line. + :param debit_values: The values of account.move.line to consider for a debit line. + :param credit_values: The values of account.move.line to consider for a credit line. :return: A dictionary: - * debit_vals: None if the line has nothing left to reconcile. - * credit_vals: None if the line has nothing left to reconcile. - * partial_vals: The newly computed values for the partial. + * debit_values: None if the line has nothing left to reconcile. + * credit_values: None if the line has nothing left to reconcile. + * partial_values: The newly computed values for the partial. + * exchange_values: The values to create an exchange difference linked to this partial. """ - def get_odoo_rate(vals): - if vals.get('record') and vals['record'].move_id.is_invoice(include_receipts=True): - exchange_rate_date = vals['record'].move_id.invoice_date + def get_odoo_rate(aml, currency): + if aml.move_id.is_invoice(include_receipts=True): + exchange_rate_date = aml.move_id.invoice_date else: - exchange_rate_date = vals['date'] - return recon_currency._get_conversion_rate(company_currency, recon_currency, vals['company'], exchange_rate_date) + exchange_rate_date = aml.date + return currency._get_conversion_rate(aml.company_currency_id, currency, aml.company_id, exchange_rate_date) - def get_accounting_rate(vals): - if company_currency.is_zero(vals['balance']) or vals['currency'].is_zero(vals['amount_currency']): - return None - else: - return abs(vals['amount_currency']) / abs(vals['balance']) + def get_accounting_rate(aml): + if not aml.company_currency_id.is_zero(aml.balance) and not aml.currency_id.is_zero(aml.amount_currency): + return abs(aml.amount_currency) / abs(aml.balance) # ==== Determine the currency in which the reconciliation will be done ==== # In this part, we retrieve the residual amounts, check if they are zero or not and determine in which # currency and at which rate the reconciliation will be done. - res = { - 'debit_vals': debit_vals, - 'credit_vals': credit_vals, + 'debit_values': debit_values, + 'credit_values': credit_values, } - remaining_debit_amount_curr = debit_vals['amount_residual_currency'] - remaining_credit_amount_curr = credit_vals['amount_residual_currency'] - remaining_debit_amount = debit_vals['amount_residual'] - remaining_credit_amount = credit_vals['amount_residual'] + remaining_debit_amount_curr = debit_values['amount_residual_currency'] + remaining_credit_amount_curr = credit_values['amount_residual_currency'] + remaining_debit_amount = debit_values['amount_residual'] + remaining_credit_amount = credit_values['amount_residual'] - company_currency = debit_vals['company'].currency_id + debit_aml = debit_values['aml'] + credit_aml = credit_values['aml'] + company_currency = debit_aml.company_currency_id has_debit_zero_residual = company_currency.is_zero(remaining_debit_amount) has_credit_zero_residual = company_currency.is_zero(remaining_credit_amount) - has_debit_zero_residual_currency = debit_vals['currency'].is_zero(remaining_debit_amount_curr) - has_credit_zero_residual_currency = credit_vals['currency'].is_zero(remaining_credit_amount_curr) - is_rec_pay_account = debit_vals.get('record') \ - and debit_vals['record'].account_type in ('asset_receivable', 'liability_payable') + has_debit_zero_residual_currency = debit_aml.currency_id.is_zero(remaining_debit_amount_curr) + has_credit_zero_residual_currency = credit_aml.currency_id.is_zero(remaining_credit_amount_curr) + is_rec_pay_account = debit_aml.account_type in ('asset_receivable', 'liability_payable') - if debit_vals['currency'] == credit_vals['currency'] == company_currency \ + if debit_aml.currency_id == credit_aml.currency_id == company_currency \ and not has_debit_zero_residual \ and not has_credit_zero_residual: # Everything is expressed in company's currency and there is something left to reconcile. @@ -1662,42 +1661,42 @@ class AccountMoveLine(models.Model): debit_rate = credit_rate = 1.0 recon_debit_amount = remaining_debit_amount recon_credit_amount = -remaining_credit_amount - elif debit_vals['currency'] == company_currency \ + elif debit_aml.currency_id == company_currency \ and is_rec_pay_account \ and not has_debit_zero_residual \ - and credit_vals['currency'] != company_currency \ + and credit_aml.currency_id != company_currency \ and not has_credit_zero_residual_currency: # The credit line is using a foreign currency but not the opposite line. # In that case, convert the amount in company currency to the foreign currency one. - recon_currency = credit_vals['currency'] - debit_rate = get_odoo_rate(debit_vals) - credit_rate = get_accounting_rate(credit_vals) + recon_currency = credit_aml.currency_id + debit_rate = get_odoo_rate(debit_aml, recon_currency) + credit_rate = get_accounting_rate(credit_aml) recon_debit_amount = recon_currency.round(remaining_debit_amount * debit_rate) recon_credit_amount = -remaining_credit_amount_curr - elif debit_vals['currency'] != company_currency \ + elif debit_aml.currency_id != company_currency \ and is_rec_pay_account \ and not has_debit_zero_residual_currency \ - and credit_vals['currency'] == company_currency \ + and credit_aml.currency_id == company_currency \ and not has_credit_zero_residual: # The debit line is using a foreign currency but not the opposite line. # In that case, convert the amount in company currency to the foreign currency one. - recon_currency = debit_vals['currency'] - debit_rate = get_accounting_rate(debit_vals) - credit_rate = get_odoo_rate(credit_vals) + recon_currency = debit_aml.currency_id + debit_rate = get_accounting_rate(debit_aml) + credit_rate = get_odoo_rate(credit_aml, recon_currency) recon_debit_amount = remaining_debit_amount_curr recon_credit_amount = recon_currency.round(-remaining_credit_amount * credit_rate) - elif debit_vals['currency'] == credit_vals['currency'] \ - and debit_vals['currency'] != company_currency \ + elif debit_aml.currency_id == credit_aml.currency_id \ + and debit_aml.currency_id != company_currency \ and not has_debit_zero_residual_currency \ and not has_credit_zero_residual_currency: # Both lines are sharing the same foreign currency. - recon_currency = debit_vals['currency'] - debit_rate = get_accounting_rate(debit_vals) - credit_rate = get_accounting_rate(credit_vals) + recon_currency = debit_aml.currency_id + debit_rate = get_accounting_rate(debit_aml) + credit_rate = get_accounting_rate(credit_aml) recon_debit_amount = remaining_debit_amount_curr recon_credit_amount = -remaining_credit_amount_curr - elif debit_vals['currency'] == credit_vals['currency'] \ - and debit_vals['currency'] != company_currency \ + elif debit_aml.currency_id == credit_aml.currency_id \ + and debit_aml.currency_id != company_currency \ and (has_debit_zero_residual_currency or has_credit_zero_residual_currency): # Special case for exchange difference lines. In that case, both lines are sharing the same foreign # currency but at least one has no amount in foreign currency. @@ -1711,18 +1710,18 @@ class AccountMoveLine(models.Model): else: # Multiple involved foreign currencies. The reconciliation is done using the currency of the company. recon_currency = company_currency - debit_rate = get_accounting_rate(debit_vals) - credit_rate = get_accounting_rate(credit_vals) + debit_rate = get_accounting_rate(debit_aml) + credit_rate = get_accounting_rate(credit_aml) recon_debit_amount = remaining_debit_amount recon_credit_amount = -remaining_credit_amount # Check if there is something left to reconcile. Move to the next loop iteration if not. skip_reconciliation = False if recon_currency.is_zero(recon_debit_amount): - res['debit_vals'] = None + res['debit_values'] = None skip_reconciliation = True if recon_currency.is_zero(recon_credit_amount): - res['credit_vals'] = None + res['credit_values'] = None skip_reconciliation = True if skip_reconciliation: return res @@ -1742,12 +1741,12 @@ class AccountMoveLine(models.Model): # Compute the partial amount expressed in foreign currency. if debit_rate: - partial_debit_amount_currency = debit_vals['currency'].round(debit_rate * min_recon_amount) + partial_debit_amount_currency = debit_aml.currency_id.round(debit_rate * min_recon_amount) partial_debit_amount_currency = min(partial_debit_amount_currency, remaining_debit_amount_curr) else: partial_debit_amount_currency = 0.0 if credit_rate: - partial_credit_amount_currency = credit_vals['currency'].round(credit_rate * min_recon_amount) + partial_credit_amount_currency = credit_aml.currency_id.round(credit_rate * min_recon_amount) partial_credit_amount_currency = min(partial_credit_amount_currency, -remaining_credit_amount_curr) else: partial_credit_amount_currency = 0.0 @@ -1770,11 +1769,11 @@ class AccountMoveLine(models.Model): # Compute the partial amount expressed in foreign currency. # Take care to handle the case when a line expressed in company currency is mimicking the foreign # currency of the opposite line. - if debit_vals['currency'] == company_currency: + if debit_aml.currency_id == company_currency: partial_debit_amount_currency = partial_amount else: partial_debit_amount_currency = min_recon_amount - if credit_vals['currency'] == company_currency: + if credit_aml.currency_id == company_currency: partial_credit_amount_currency = partial_amount else: partial_credit_amount_currency = min_recon_amount @@ -1787,16 +1786,14 @@ class AccountMoveLine(models.Model): if recon_currency == company_currency: if debit_fully_matched: debit_exchange_amount = remaining_debit_amount_curr - partial_debit_amount_currency - if not debit_vals['currency'].is_zero(debit_exchange_amount): - if debit_vals.get('record'): - exchange_lines_to_fix += debit_vals['record'] + if not debit_aml.currency_id.is_zero(debit_exchange_amount): + exchange_lines_to_fix += debit_aml amounts_list.append({'amount_residual_currency': debit_exchange_amount}) remaining_debit_amount_curr -= debit_exchange_amount if credit_fully_matched: credit_exchange_amount = remaining_credit_amount_curr + partial_credit_amount_currency - if not credit_vals['currency'].is_zero(credit_exchange_amount): - if credit_vals.get('record'): - exchange_lines_to_fix += credit_vals['record'] + if not credit_aml.currency_id.is_zero(credit_exchange_amount): + exchange_lines_to_fix += credit_aml amounts_list.append({'amount_residual_currency': credit_exchange_amount}) remaining_credit_amount_curr += credit_exchange_amount @@ -1805,11 +1802,10 @@ class AccountMoveLine(models.Model): # Create an exchange difference on the remaining amount expressed in company's currency. debit_exchange_amount = remaining_debit_amount - partial_amount if not company_currency.is_zero(debit_exchange_amount): - if debit_vals.get('record'): - exchange_lines_to_fix += debit_vals['record'] + exchange_lines_to_fix += debit_aml amounts_list.append({'amount_residual': debit_exchange_amount}) remaining_debit_amount -= debit_exchange_amount - if debit_vals['currency'] == company_currency: + if debit_aml.currency_id == company_currency: remaining_debit_amount_curr -= debit_exchange_amount else: # Create an exchange difference ensuring the rate between the residual amounts expressed in @@ -1817,22 +1813,20 @@ class AccountMoveLine(models.Model): # 'amount_currency' & 'balance'. debit_exchange_amount = partial_debit_amount - partial_amount if company_currency.compare_amounts(debit_exchange_amount, 0.0) > 0: - if debit_vals.get('record'): - exchange_lines_to_fix += debit_vals['record'] + exchange_lines_to_fix += debit_aml amounts_list.append({'amount_residual': debit_exchange_amount}) remaining_debit_amount -= debit_exchange_amount - if debit_vals['currency'] == company_currency: + if debit_aml.currency_id == company_currency: remaining_debit_amount_curr -= debit_exchange_amount if credit_fully_matched: # Create an exchange difference on the remaining amount expressed in company's currency. credit_exchange_amount = remaining_credit_amount + partial_amount if not company_currency.is_zero(credit_exchange_amount): - if credit_vals.get('record'): - exchange_lines_to_fix += credit_vals['record'] + exchange_lines_to_fix += credit_aml amounts_list.append({'amount_residual': credit_exchange_amount}) remaining_credit_amount += credit_exchange_amount - if credit_vals['currency'] == company_currency: + if credit_aml.currency_id == company_currency: remaining_credit_amount_curr -= credit_exchange_amount else: # Create an exchange difference ensuring the rate between the residual amounts expressed in @@ -1840,17 +1834,16 @@ class AccountMoveLine(models.Model): # 'amount_currency' & 'balance'. credit_exchange_amount = partial_amount - partial_credit_amount if company_currency.compare_amounts(credit_exchange_amount, 0.0) < 0: - if credit_vals.get('record'): - exchange_lines_to_fix += credit_vals['record'] + exchange_lines_to_fix += credit_aml amounts_list.append({'amount_residual': credit_exchange_amount}) remaining_credit_amount -= credit_exchange_amount - if credit_vals['currency'] == company_currency: + if credit_aml.currency_id == company_currency: remaining_credit_amount_curr -= credit_exchange_amount if exchange_lines_to_fix: - res['exchange_vals'] = exchange_lines_to_fix._prepare_exchange_difference_move_vals( + res['exchange_values'] = exchange_lines_to_fix._prepare_exchange_difference_move_vals( amounts_list, - exchange_date=max(debit_vals['date'], credit_vals['date']), + exchange_date=max(debit_aml.date, credit_aml.date), ) # ==== Create partials ==== @@ -1860,71 +1853,464 @@ class AccountMoveLine(models.Model): remaining_debit_amount_curr -= partial_debit_amount_currency remaining_credit_amount_curr += partial_credit_amount_currency - res['partial_vals'] = { + res['partial_values'] = { 'amount': partial_amount, 'debit_amount_currency': partial_debit_amount_currency, 'credit_amount_currency': partial_credit_amount_currency, - 'debit_move_id': debit_vals.get('record') and debit_vals['record'].id, - 'credit_move_id': credit_vals.get('record') and credit_vals['record'].id, + 'debit_move_id': debit_aml.id, + 'credit_move_id': credit_aml.id, } - debit_vals['amount_residual'] = remaining_debit_amount - debit_vals['amount_residual_currency'] = remaining_debit_amount_curr - credit_vals['amount_residual'] = remaining_credit_amount - credit_vals['amount_residual_currency'] = remaining_credit_amount_curr + debit_values['amount_residual'] = remaining_debit_amount + debit_values['amount_residual_currency'] = remaining_debit_amount_curr + credit_values['amount_residual'] = remaining_credit_amount + credit_values['amount_residual_currency'] = remaining_credit_amount_curr if debit_fully_matched: - res['debit_vals'] = None + res['debit_values'] = None if credit_fully_matched: - res['credit_vals'] = None + res['credit_values'] = None return res @api.model - def _prepare_reconciliation_partials(self, vals_list): - ''' Prepare the partials on the current journal items to perform the reconciliation. + def _prepare_reconciliation_amls(self, values_list): + """ Prepare the partials on the current journal items to perform the reconciliation. Note: The order of records in self is important because the journal items will be reconciled using this order. - :return: a tuple of 1) list of vals for partial reconciliation creation, 2) the list of vals for the exchange difference entries to be created - ''' - debit_vals_list = iter([x for x in vals_list if x['balance'] > 0.0 or x['amount_currency'] > 0.0]) - credit_vals_list = iter([x for x in vals_list if x['balance'] < 0.0 or x['amount_currency'] < 0.0]) - debit_vals = None - credit_vals = None - partials_vals_list = [] - exchange_data = {} + :return: a tuple of + 1) list of vals for partial reconciliation creation, + 2) the list of vals for the exchange difference entries to be created + """ + debit_values_list = iter([x for x in values_list if x['aml'].balance > 0.0 or x['aml'].amount_currency > 0.0]) + credit_values_list = iter([x for x in values_list if x['aml'].balance < 0.0 or x['aml'].amount_currency < 0.0]) + debit_values = None + credit_values = None + fully_reconciled_aml_ids = set() + all_results = [] while True: # ==== Find the next available lines ==== # For performance reasons, the partials are created all at once meaning the residual amounts can't be # trusted from one iteration to another. That's the reason why all residual amounts are kept as variables - # and reduced "manually" every time we append a dictionary to 'partials_vals_list'. + # and reduced "manually" every time we append a dictionary to 'partials_values_list'. # Move to the next available debit line. - if not debit_vals: - debit_vals = next(debit_vals_list, None) - if not debit_vals: + if not debit_values: + debit_values = next(debit_values_list, None) + if not debit_values: break # Move to the next available credit line. - if not credit_vals: - credit_vals = next(credit_vals_list, None) - if not credit_vals: + if not credit_values: + credit_values = next(credit_values_list, None) + if not credit_values: break # ==== Compute the amounts to reconcile ==== - res = self._prepare_reconciliation_single_partial(debit_vals, credit_vals) - if res.get('partial_vals'): - if res.get('exchange_vals'): - exchange_data[len(partials_vals_list)] = res['exchange_vals'] - partials_vals_list.append(res['partial_vals']) - if res['debit_vals'] is None: - debit_vals = None - if res['credit_vals'] is None: - credit_vals = None + results = self._prepare_reconciliation_single_partial(debit_values, credit_values) + if results.get('partial_values'): + all_results.append(results) + if results['debit_values'] is None: + fully_reconciled_aml_ids.add(debit_values['aml'].id) + debit_values = None + if results['credit_values'] is None: + fully_reconciled_aml_ids.add(credit_values['aml'].id) + credit_values = None - return partials_vals_list, exchange_data + return all_results, fully_reconciled_aml_ids + + @api.model + def _prepare_reconciliation_plan(self, plan, amls_values_map): + """ Perform virtually the reconciliation of the plan passed as parameter. + + :param plan: The plan to know which lines to reconcile in which order. + :param amls_values_map: A mapping aml => amount_residual/amount_residual_currency + :return: A list of all results returned by the '_prepare_reconciliation_amls' method. + """ + all_fully_reconciled_aml_ids = set() + all_results = [] + + def process_amls(amls): + remaining_amls = amls.filtered(lambda aml: aml.id not in all_fully_reconciled_aml_ids) + amls_results, fully_reconciled_aml_ids = self._prepare_reconciliation_amls([ + amls_values_map[aml] + for aml in remaining_amls + ]) + all_fully_reconciled_aml_ids.update(fully_reconciled_aml_ids) + for amls_result in amls_results: + all_results.append(amls_result) + + def process_leaf(plan_node): + # Sub plan to evaluate. + for child_node in plan_node.get('nodes', []): + process_leaf(child_node) + + # Group of amls to evaluate. + process_amls(plan_node['amls']) + + process_leaf(plan) + return all_results + + @api.model + def _optimize_reconciliation_plan(self, reconciliation_plan): + """ Decode the initial reconciliation plan passed as parameter and converted it into a list of tree depicting + the way the reconciliation should be done. + Also, this method is responsible sorting the amls and splitting them by currency. + Then, this method checks the parameter to ensure we are not going to perform any invalid reconciliation like + a cross-account/cross-company partial. + + The split by currencies is made as follows. + Suppose account.move.line(1, 2) are expressed in currency1 and account.move.line(3, 4) are expressed + in currency2. + If the reconciliation plan is [account.move.line(1, 2, 3, 4)], the optimizer will convert it into: + [[account.move.line(1, 2), account.move.line(3, 4)]] + + :param reconciliation_plan: A list of reconciliation to perform. + :return: A list of dictionaries containing: + * amls: A recordset. + * aml_ids: The recordset ids. + * nodes: A list of sub-nodes. + """ + + def process_amls(amls): + sorted_amls = amls.sorted(key=lambda aml: ( + aml.date_maturity or aml.date, + aml.currency_id, + aml.amount_currency, + aml.balance, + )) + currencies = sorted_amls.currency_id + + results = { + 'amls': sorted_amls, + 'aml_ids': set(sorted_amls.ids), + } + + if len(currencies) != 1: + nodes = results['nodes'] = [] + for currency in currencies: + amls_in_currency = sorted_amls.filtered(lambda x: x.currency_id == currency) + nodes.append({ + 'amls': amls_in_currency, + 'aml_ids': set(amls_in_currency.ids), + }) + return results + + def process_children(children): + node = { + 'nodes': [], + 'aml_ids': set(), + } + for child in children: + results = process_leaf(child) + if results: + node['nodes'].append(results) + node['aml_ids'].update(results['aml_ids']) + node['amls'] = self.browse(node['aml_ids']) + return node + + def process_leaf(item): + if not item: + return + + if isinstance(item, models.BaseModel): + # Group of amls to evaluate. + return process_amls(item) + else: + # Sub plan to evaluate. + return process_children(item) + + plan_list = [] + all_aml_ids = set() + for item in reconciliation_plan: + plan_node = process_leaf(item) + if not plan_node or not plan_node.get('amls'): + continue + + # Check the amls to be reconciled all together. + amls = plan_node['amls'] + if any(aml.reconciled for aml in amls): + raise UserError(_("You are trying to reconcile some entries that are already reconciled.")) + if any(aml.parent_state != 'posted' for aml in amls): + raise UserError(_("You can only reconcile posted entries.")) + if len(amls.account_id) > 1: + raise UserError(_( + "Entries are not from the same account: %s", + ", ".join(amls.account_id.mapped('display_name')), + )) + if len(amls.company_id) > 1: + raise UserError(_( + "Entries doesn't belong to the same company: %s", + ", ".join(amls.company_id.mapped('display_name')), + )) + + if any( + not aml.account_id.reconcile + and aml.account_id.account_type not in ('asset_cash', 'liability_credit_card') + for aml in amls + ): + raise UserError(_( + "Account %s does not allow reconciliation. First change the configuration of this account " + "to allow it.", + amls.account_id.display_name + )) + + plan_list.append(plan_node) + all_aml_ids.update(plan_node['aml_ids']) + + return plan_list, self.browse(all_aml_ids) + + def _reconcile_pre_hook(self): + not_paid_invoices = self.move_id.filtered(lambda move: + move.is_invoice(include_receipts=True) + and move.payment_state not in ('paid', 'in_payment') + ) + return {'not_paid_invoices': not_paid_invoices} + + def _reconcile_post_hook(self, data): + data['not_paid_invoices']\ + .filtered(lambda move: move.payment_state in ('paid', 'in_payment'))\ + ._invoice_paid_hook() + + @api.model + def _reconcile_plan(self, reconciliation_plan): + """ Reconcile the amls following the reconciliation plan. + The plan passed as parameter is a list of either a recordset of amls, either another plan. + + For example: + [account.move.line(1, 2), account.move.line(3, 4)] means: + - account.move.line(1, 2) will be reconciled first. + - account.move.line(3, 4) will be reconciled after. + + [[account.move.line(1, 2), account.move.line(3, 4)]] means: + - account.move.line(1, 2) will be reconciled first. + - account.move.line(3, 4) will be reconciled after. + - account.move.line(1, 2, 3, 4).filtered(lambda x: not x.reconciled) will be reconciled at the end. + + :param reconciliation_plan: A list of reconciliation to perform. + """ + # Parameter allowing to disable the exchange journal entries on partials. + disable_partial_exchange_diff = bool(self.env['ir.config_parameter'].sudo().get_param('account.disable_partial_exchange_diff')) + + # ==== Prepare the reconciliation ==== + # Batch the amls all together to know what should be reconciled and when. + plan_list, all_amls = self._optimize_reconciliation_plan(reconciliation_plan) + + # ==== Prefetch the fields all at once to speedup the reconciliation ==== + # All of those fields will be cached by the orm. Since the amls are split into multiple batches, the orm is not + # able to prefetch the data for all of them at once. For that reason, we force the orm to populate the cache + # before doing anything. + all_amls.move_id + all_amls.matched_debit_ids + all_amls.matched_credit_ids + + # ==== Track the invoice's state to call the hook when they become paid ==== + pre_hook_data = all_amls._reconcile_pre_hook() + + # ==== Collect amls data ==== + # All residual amounts are collected and updated until the creation of partials in batch. + # This is done that way to minimize the orm time for fields invalidation/mark as recompute and + # recomputation. + aml_values_map = { + aml: { + 'aml': aml, + 'amount_residual': aml.amount_residual, + 'amount_residual_currency': aml.amount_residual_currency, + } + for aml in all_amls + } + + # ==== Prepare the partials ==== + partials_values_list = [] + exchange_diff_values_list = [] + exchange_diff_partial_index = [] + all_plan_results = [] + partial_index = 0 + for plan in plan_list: + plan_results = self\ + .with_context(no_exchange_difference=self._context.get('no_exchange_difference') or disable_partial_exchange_diff)\ + ._prepare_reconciliation_plan(plan, aml_values_map) + all_plan_results.append(plan_results) + for results in plan_results: + partials_values_list.append(results['partial_values']) + if results.get('exchange_values') and results['exchange_values']['move_values']['line_ids']: + exchange_diff_values_list.append(results['exchange_values']) + exchange_diff_partial_index.append(partial_index) + partial_index += 1 + + # ==== Create the partials ==== + # Link the newly created partials to the plan. There are needed later for caba exchange entries. + partials = self.env['account.partial.reconcile'].create(partials_values_list) + start_range = 0 + for plan_results, plan in zip(all_plan_results, plan_list): + size = len(plan_results) + plan['partials'] = partials[start_range:start_range + size] + start_range += size + + # ==== Create the partial exchange journal entries ==== + exchange_moves = self._create_exchange_difference_moves(exchange_diff_values_list) + for index, exchange_move in zip(exchange_diff_partial_index, exchange_moves): + partials[index].exchange_move_id = exchange_move + + # ==== Create entries for cash basis taxes ==== + def is_cash_basis_needed(account): + return account.company_id.tax_exigibility \ + and account.account_type in ('asset_receivable', 'liability_payable') + + if not self._context.get('move_reverse_cancel'): + for plan in plan_list: + if is_cash_basis_needed(plan['amls'].account_id): + plan['partials']._create_tax_cash_basis_moves() + + # ==== Prepare full reconcile creation ==== + # First, we need to find all sub-set of amls that are candidates for a full. + + def is_line_reconciled(aml, has_multiple_currencies): + # Check if the journal item passed as parameter is now fully reconciled. + if aml.reconciled: + return True + if not aml.matched_debit_ids and not aml.matched_credit_ids: + # Suppose a journal item having balance = 0 but an amount_currency like an exchange difference. + return False + if has_multiple_currencies: + return aml.company_currency_id.is_zero(aml.amount_residual) + else: + return aml.currency_id.is_zero(aml.amount_residual_currency) + + full_batches = [] + all_aml_ids = set() + for plan in plan_list: + for aml in plan['amls']: + if 'full_batch_index' in aml_values_map[aml]: + continue + + involved_amls = plan['amls']._all_reconciled_lines() + all_aml_ids.update(involved_amls.ids) + full_batch_index = len(full_batches) + has_multiple_currencies = len(involved_amls.currency_id) > 1 + is_fully_reconciled = all( + is_line_reconciled(involved_aml, has_multiple_currencies) + for involved_aml in involved_amls + ) + full_batches.append({ + 'amls': involved_amls, + 'is_fully_reconciled': is_fully_reconciled, + }) + for involved_aml in involved_amls: + if aml_values_map.get(involved_aml): + aml_values_map[involved_aml]['full_batch_index'] = full_batch_index + + # ==== Prefetch the fields all at once to speedup the reconciliation ==== + # Again, we do the same optimization for the prefetching. We need to do it again since most of the values have + # been invalidated with the creation of the account.partial.reconcile records. + all_amls = self.browse(list(all_aml_ids)) + all_amls.move_id + all_amls.matched_debit_ids + all_amls.matched_credit_ids + + # ==== Prepare the full exchange journal entries ==== + # This part could be bypassed using the 'no_exchange_difference' key inside the context. This is useful + # when importing a full accounting including the reconciliation like Winbooks. + + exchange_diff_values_list = [] + exchange_diff_full_batch_index = [] + if not self._context.get('no_exchange_difference'): + for fulL_batch_index, full_batch in enumerate(full_batches): + involved_amls = full_batch['amls'] + if not full_batch['is_fully_reconciled']: + continue + + # In normal cases, the exchange differences are already generated by the partial at this point meaning + # there is no journal item left with a zero amount residual in one currency but not in the other. + # However, after a migration coming from an older version with an older partial reconciliation or due to + # some rounding issues (when dealing with different decimal places for example), we could need an extra + # exchange difference journal entry to handle them. + exchange_lines_to_fix = self.env['account.move.line'] + amounts_list = [] + exchange_max_date = date.min + for aml in involved_amls: + if not aml.company_currency_id.is_zero(aml.amount_residual): + exchange_lines_to_fix += aml + amounts_list.append({'amount_residual': aml.amount_residual}) + elif not aml.currency_id.is_zero(aml.amount_residual_currency): + exchange_lines_to_fix += aml + amounts_list.append({'amount_residual_currency': aml.amount_residual_currency}) + exchange_max_date = max(exchange_max_date, aml.date) + exchange_diff_values = exchange_lines_to_fix._prepare_exchange_difference_move_vals( + amounts_list, + company=involved_amls.company_id, + exchange_date=exchange_max_date, + ) + + # Exchange difference for cash basis entries. + caba_lines_to_reconcile = None + if is_cash_basis_needed(involved_amls.account_id): + caba_lines_to_reconcile = involved_amls._add_exchange_difference_cash_basis_vals(exchange_diff_values) + + # Prepare the exchange difference. + if exchange_diff_values['move_values']['line_ids']: + exchange_diff_full_batch_index.append(fulL_batch_index) + exchange_diff_values_list.append(exchange_diff_values) + full_batch['caba_lines_to_reconcile'] = caba_lines_to_reconcile + + # ==== Create the full exchange journal entries ==== + exchange_moves = self._create_exchange_difference_moves(exchange_diff_values_list) + for fulL_batch_index, exchange_move in zip(exchange_diff_full_batch_index, exchange_moves): + full_batch = full_batches[full_batch_index] + amls = full_batch['amls'] + full_batch['exchange_move'] = exchange_move + exchange_move_lines = exchange_move.line_ids.filtered(lambda line: line.account_id == amls.account_id) + full_batch['amls'] |= exchange_move_lines + + # ==== Create the full reconcile ==== + # Note we are using Command.link and not Command.set because Command.set is triggering an unlink that is + # slowing down the assignation of the co-fields. Indeed, unlink is forcing a flush. + full_reconcile_values_list = [] + full_reconcile_fulL_batch_index = [] + for fulL_batch_index, full_batch in enumerate(full_batches): + amls = full_batch['amls'] + involved_partials = amls.matched_debit_ids + amls.matched_credit_ids + if full_batch['is_fully_reconciled']: + full_reconcile_values_list.append({ + 'exchange_move_id': full_batch.get('exchange_move') and full_batch['exchange_move'].id, + 'partial_reconcile_ids': [Command.link(partial.id) for partial in involved_partials], + 'reconciled_line_ids': [Command.link(aml.id) for aml in amls], + }) + full_reconcile_fulL_batch_index.append(fulL_batch_index) + + self.env['account.full.reconcile']\ + .with_context( + skip_invoice_sync=True, + skip_invoice_line_sync=True, + skip_account_move_synchronization=True, + check_move_validity=False, + )\ + .create(full_reconcile_values_list) + + # === Cash basis rounding autoreconciliation === + # In case a cash basis rounding difference line got created for the transition account, we reconcile it with the corresponding lines + # on the cash basis moves (so that it reaches full reconciliation and creates an exchange difference entry for this account as well) + for fulL_batch_index, full_batch in enumerate(full_batches): + if not full_batch.get('caba_lines_to_reconcile'): + continue + + caba_lines_to_reconcile = full_batch['caba_lines_to_reconcile'] + exchange_move = full_batch['exchange_move'] + for (dummy, account, repartition_line), amls_to_reconcile in caba_lines_to_reconcile.items(): + if not account.reconcile: + continue + + exchange_line = exchange_move.line_ids.filtered( + lambda l: l.account_id == account and l.tax_repartition_line_id == repartition_line + ) + + (exchange_line + amls_to_reconcile)\ + .filtered(lambda l: not l.reconciled)\ + .reconcile() + + all_amls._reconcile_post_hook(pre_hook_data) def _create_reconciliation_partials(self): '''create the partial reconciliation between all the records in self @@ -1932,22 +2318,17 @@ class AccountMoveLine(models.Model): ''' partials_vals_list, exchange_data = self._prepare_reconciliation_partials([ { - 'record': line, - 'balance': line.balance, - 'amount_currency': line.amount_currency, + 'aml': line, 'amount_residual': line.amount_residual, 'amount_residual_currency': line.amount_residual_currency, - 'company': line.company_id, - 'currency': line.currency_id, - 'date': line.date, } for line in self ]) partials = self.env['account.partial.reconcile'].create(partials_vals_list) # ==== Create exchange difference moves ==== - for index, exchange_vals in exchange_data.items(): - partials[index].exchange_move_id = self._create_exchange_difference_move(exchange_vals) + for index, exchange_values in exchange_data.items(): + partials[index].exchange_move_id = self._create_exchange_difference_move(exchange_values) return partials @@ -2030,47 +2411,69 @@ class AccountMoveLine(models.Model): ] to_reconcile.append((line, sequence)) - return {'move_vals': move_vals, 'to_reconcile': to_reconcile} + return {'move_values': move_vals, 'to_reconcile': to_reconcile} @api.model - def _create_exchange_difference_move(self, exchange_diff_vals): + def _create_exchange_difference_moves(self, exchange_diff_values_list): """ Create the exchange difference journal entry on the current journal items. - :param exchange_diff_vals: The current vals of the exchange difference journal entry created by the - '_prepare_exchange_difference_move_vals' method. - :return: An account.move record. + + :param exchange_diff_values_list: A list of values to create and reconcile the exchange differences + See the '_prepare_exchange_difference_move_vals' method. + :return: An account.move recordset. """ - move_vals = exchange_diff_vals['move_vals'] - if not move_vals['line_ids']: - return + exchange_move_values_list = [] + journal_ids = set() + for exchange_diff_values in exchange_diff_values_list: + move_vals = exchange_diff_values['move_values'] + exchange_move_values_list.append(move_vals) - # Check the configuration of the exchange difference journal. - journal = self.env['account.journal'].browse(move_vals['journal_id']) - if not journal: - raise UserError(_( - "You should configure the 'Exchange Gain or Loss Journal' in your company settings, to manage" - " automatically the booking of accounting entries related to differences between exchange rates." - )) - if not journal.company_id.expense_currency_exchange_account_id: - raise UserError(_( - "You should configure the 'Loss Exchange Rate Account' in your company settings, to manage" - " automatically the booking of accounting entries related to differences between exchange rates." - )) - if not journal.company_id.income_currency_exchange_account_id.id: - raise UserError(_( - "You should configure the 'Gain Exchange Rate Account' in your company settings, to manage" - " automatically the booking of accounting entries related to differences between exchange rates." - )) + if not move_vals['journal_id']: + raise UserError(_( + "You have to configure the 'Exchange Gain or Loss Journal' in your company settings, to manage" + " automatically the booking of accounting entries related to differences between exchange rates." + )) - # Create the move. - exchange_move = self.env['account.move'].with_context(skip_invoice_sync=True).create(move_vals) - exchange_move._post(soft=False) + journal_ids.add(move_vals['journal_id']) - # Reconcile lines to the newly created exchange difference journal entry by creating more partials. - for source_line, sequence in exchange_diff_vals['to_reconcile']: - exchange_diff_line = exchange_move.line_ids[sequence] - (exchange_diff_line + source_line).with_context(no_exchange_difference=True).reconcile() + if not exchange_move_values_list: + return self.env['account.move'] - return exchange_move + # ==== Check the config ==== + journals = self.env['account.journal'].browse(list(journal_ids)) + for journal in journals: + if not journal.company_id.expense_currency_exchange_account_id: + raise UserError(_( + "You should configure the 'Loss Exchange Rate Account' in your company settings, to manage" + " automatically the booking of accounting entries related to differences between exchange rates." + )) + if not journal.company_id.income_currency_exchange_account_id.id: + raise UserError(_( + "You should configure the 'Gain Exchange Rate Account' in your company settings, to manage" + " automatically the booking of accounting entries related to differences between exchange rates." + )) + + # ==== Create the move ==== + exchange_moves = self.env['account.move']\ + .with_context( + skip_invoice_sync=True, + skip_invoice_line_sync=True, + skip_account_move_synchronization=True, + )\ + .create(exchange_move_values_list) + exchange_moves._post(soft=False) + + # ==== Reconcile ==== + reconciliation_plan = [] + for exchange_move, exchange_diff_values in zip(exchange_moves, exchange_diff_values_list): + for source_line, sequence in exchange_diff_values['to_reconcile']: + exchange_diff_line = exchange_move.line_ids[sequence] + reconciliation_plan.append((source_line + exchange_diff_line)) + + self\ + .with_context(no_exchange_difference=True)\ + ._reconcile_plan(reconciliation_plan) + + return exchange_moves def _add_exchange_difference_cash_basis_vals(self, exchange_diff_vals): """ Generate the exchange difference values used to create the journal items @@ -2089,7 +2492,7 @@ class AccountMoveLine(models.Model): '_prepare_exchange_difference_move_vals' method. """ caba_lines_to_reconcile = defaultdict(lambda: self.env['account.move.line']) # in the form {(move, account, repartition_line): move_lines} - move_vals = exchange_diff_vals['move_vals'] + move_vals = exchange_diff_vals['move_values'] for move in self.move_id: account_vals_to_fix = {} @@ -2278,168 +2681,8 @@ class AccountMoveLine(models.Model): return caba_lines_to_reconcile def reconcile(self): - ''' Reconcile the current move lines all together. - :return: A dictionary representing a summary of what has been done during the reconciliation: - * partials: A recorset of all account.partial.reconcile created during the reconciliation. - * exchange_partials: A recorset of all account.partial.reconcile created during the reconciliation - with the exchange difference journal entries. - * full_reconcile: An account.full.reconcile record created when there is nothing left to reconcile - in the involved lines. - * tax_cash_basis_moves: An account.move recordset representing the tax cash basis journal entries. - ''' - results = {'exchange_partials': self.env['account.partial.reconcile']} - - if not self: - return results - - not_paid_invoices = self.move_id.filtered(lambda move: - move.is_invoice(include_receipts=True) - and move.payment_state not in ('paid', 'in_payment') - ) - - # ==== Check the lines can be reconciled together ==== - company = None - account = None - for line in self: - if line.reconciled: - raise UserError(_("You are trying to reconcile some entries that are already reconciled.")) - if not line.account_id.reconcile and line.account_id.account_type not in ('asset_cash', 'liability_credit_card'): - raise UserError(_("Account %s does not allow reconciliation. First change the configuration of this account to allow it.") - % line.account_id.display_name) - if line.move_id.state != 'posted': - raise UserError(_('You can only reconcile posted entries.')) - if company is None: - company = line.company_id - elif line.company_id != company: - raise UserError(_("Entries doesn't belong to the same company: %s != %s") - % (company.display_name, line.company_id.display_name)) - if account is None: - account = line.account_id - elif line.account_id != account: - raise UserError(_("Entries are not from the same account: %s != %s") - % (account.display_name, line.account_id.display_name)) - - sorted_lines = self.sorted(key=lambda line: (line.date_maturity or line.date, line.currency_id, line.amount_currency)) - - # ==== Collect all involved lines through the existing reconciliation ==== - - involved_lines = sorted_lines._all_reconciled_lines() - involved_partials = involved_lines.matched_credit_ids | involved_lines.matched_debit_ids - - # ==== Create partials ==== - - partial_no_exch_diff = bool(self.env['ir.config_parameter'].sudo().get_param('account.disable_partial_exchange_diff')) - sorted_lines_ctx = sorted_lines.with_context(no_exchange_difference=self._context.get('no_exchange_difference') or partial_no_exch_diff) - partials = sorted_lines_ctx._create_reconciliation_partials() - results['partials'] = partials - involved_partials += partials - exchange_move_lines = partials.exchange_move_id.line_ids.filtered(lambda line: line.account_id == account) - involved_lines += exchange_move_lines - exchange_diff_partials = exchange_move_lines.matched_debit_ids + exchange_move_lines.matched_credit_ids - involved_partials += exchange_diff_partials - results['exchange_partials'] += exchange_diff_partials - - # ==== Create entries for cash basis taxes ==== - - is_cash_basis_needed = account.company_id.tax_exigibility and account.account_type in ('asset_receivable', 'liability_payable') - if is_cash_basis_needed and not self._context.get('move_reverse_cancel'): - tax_cash_basis_moves = partials._create_tax_cash_basis_moves() - results['tax_cash_basis_moves'] = tax_cash_basis_moves - - # ==== Check if a full reconcile is needed ==== - - def is_line_reconciled(line, has_multiple_currencies): - # Check if the journal item passed as parameter is now fully reconciled. - return line.reconciled \ - or (line.company_currency_id.is_zero(line.amount_residual) - if has_multiple_currencies - else line.currency_id.is_zero(line.amount_residual_currency) - ) - - has_multiple_currencies = len(involved_lines.currency_id) > 1 - if all(is_line_reconciled(line, has_multiple_currencies) for line in involved_lines): - # ==== Create the exchange difference move ==== - # This part could be bypassed using the 'no_exchange_difference' key inside the context. This is useful - # when importing a full accounting including the reconciliation like Winbooks. - - exchange_move = self.env['account.move'] - caba_lines_to_reconcile = None - if not self._context.get('no_exchange_difference'): - # In normal cases, the exchange differences are already generated by the partial at this point meaning - # there is no journal item left with a zero amount residual in one currency but not in the other. - # However, after a migration coming from an older version with an older partial reconciliation or due to - # some rounding issues (when dealing with different decimal places for example), we could need an extra - # exchange difference journal entry to handle them. - exchange_lines_to_fix = self.env['account.move.line'] - amounts_list = [] - exchange_max_date = date.min - for line in involved_lines: - if not line.company_currency_id.is_zero(line.amount_residual): - exchange_lines_to_fix += line - amounts_list.append({'amount_residual': line.amount_residual}) - elif not line.currency_id.is_zero(line.amount_residual_currency): - exchange_lines_to_fix += line - amounts_list.append({'amount_residual_currency': line.amount_residual_currency}) - exchange_max_date = max(exchange_max_date, line.date) - exchange_diff_vals = exchange_lines_to_fix._prepare_exchange_difference_move_vals( - amounts_list, - company=involved_lines[0].company_id, - exchange_date=exchange_max_date, - ) - - # Exchange difference for cash basis entries. - if is_cash_basis_needed: - caba_lines_to_reconcile = involved_lines._add_exchange_difference_cash_basis_vals(exchange_diff_vals) - - # Create the exchange difference. - if exchange_diff_vals['move_vals']['line_ids']: - exchange_move = involved_lines._create_exchange_difference_move(exchange_diff_vals) - if exchange_move: - exchange_move_lines = exchange_move.line_ids.filtered(lambda line: line.account_id == account) - - # Track newly created lines. - involved_lines += exchange_move_lines - - # Track newly created partials. - exchange_diff_partials = exchange_move_lines.matched_debit_ids \ - + exchange_move_lines.matched_credit_ids - involved_partials += exchange_diff_partials - results['exchange_partials'] += exchange_diff_partials - - # ==== Create the full reconcile ==== - results['full_reconcile'] = self.env['account.full.reconcile'] \ - .with_context( - skip_invoice_sync=True, - skip_invoice_line_sync=True, - skip_account_move_synchronization=True, - check_move_validity=False, - ) \ - .create({ - 'exchange_move_id': exchange_move and exchange_move.id, - 'partial_reconcile_ids': [Command.set(involved_partials.ids)], - 'reconciled_line_ids': [Command.set(involved_lines.ids)], - }) - - # === Cash basis rounding autoreconciliation === - # In case a cash basis rounding difference line got created for the transition account, we reconcile it with the corresponding lines - # on the cash basis moves (so that it reaches full reconciliation and creates an exchange difference entry for this account as well) - - if caba_lines_to_reconcile: - for (dummy, account, repartition_line), amls_to_reconcile in caba_lines_to_reconcile.items(): - if not account.reconcile: - continue - - exchange_line = exchange_move.line_ids.filtered( - lambda l: l.account_id == account and l.tax_repartition_line_id == repartition_line - ) - - (exchange_line + amls_to_reconcile).filtered(lambda l: not l.reconciled).reconcile() - - not_paid_invoices.filtered(lambda move: - move.payment_state in ('paid', 'in_payment') - )._invoice_paid_hook() - - return results + """ Reconcile the current move lines all together. """ + return self._reconcile_plan([self]) def remove_move_reconcile(self): """ Undo a reconciliation """ diff --git a/addons/account/models/account_partial_reconcile.py b/addons/account/models/account_partial_reconcile.py index bb5a74c5b7d..df235667645 100644 --- a/addons/account/models/account_partial_reconcile.py +++ b/addons/account/models/account_partial_reconcile.py @@ -520,10 +520,17 @@ class AccountPartialReconcile(models.Model): moves_to_create.append(move_vals) - moves = self.env['account.move'].create(moves_to_create) + moves = self.env['account.move']\ + .with_context( + skip_invoice_sync=True, + skip_invoice_line_sync=True, + skip_account_move_synchronization=True, + )\ + .create(moves_to_create) moves._post(soft=False) # Reconcile the tax lines being on a reconcile tax basis transfer account. + reconciliation_plan = [] for lines, move_index, sequence in to_reconcile_after: # In expenses, all move lines are created manually without any grouping on tax lines. @@ -538,6 +545,8 @@ class AccountPartialReconcile(models.Model): if counterpart_line.reconciled: continue - (lines + counterpart_line).reconcile() + reconciliation_plan.append((counterpart_line + lines)) + + self.env['account.move.line']._reconcile_plan(reconciliation_plan) return moves diff --git a/addons/account/tests/test_account_move_date_algorithm.py b/addons/account/tests/test_account_move_date_algorithm.py index 0d84634f76f..c06ec23b45e 100644 --- a/addons/account/tests/test_account_move_date_algorithm.py +++ b/addons/account/tests/test_account_move_date_algorithm.py @@ -151,10 +151,9 @@ class TestAccountMoveDateAlgorithm(AccountTestInvoicingCommon): (invoice + refund).action_post() self._set_lock_date('2017-01-31') - res = (invoice + refund).line_ids\ - .filtered(lambda x: x.account_id.account_type == 'asset_receivable')\ - .reconcile() - exchange_move = res['partials'].exchange_move_id + amls = (invoice + refund).line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable') + amls.reconcile() + exchange_move = amls.matched_debit_ids.exchange_move_id self.assertRecordValues(exchange_move, [{ 'date': fields.Date.from_string('2017-02-01'), @@ -167,10 +166,9 @@ class TestAccountMoveDateAlgorithm(AccountTestInvoicingCommon): refund = self._create_invoice('out_refund', '2017-01-01', currency_id=self.currency_data['currency'].id) (invoice + refund).action_post() - res = (invoice + refund).line_ids\ - .filtered(lambda x: x.account_id.account_type == 'asset_receivable')\ - .reconcile() - exchange_move = res['partials'].exchange_move_id + amls = (invoice + refund).line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable') + amls.reconcile() + exchange_move = amls.matched_debit_ids.exchange_move_id self._set_lock_date('2017-01-31') (invoice + refund).line_ids.remove_move_reconcile() diff --git a/addons/account/tests/test_account_move_entry.py b/addons/account/tests/test_account_move_entry.py index 0b9c45f7073..a3d5bcedb01 100644 --- a/addons/account/tests/test_account_move_entry.py +++ b/addons/account/tests/test_account_move_entry.py @@ -898,12 +898,11 @@ class TestAccountMove(AccountTestInvoicingCommon): ]) moves.action_post() - res = moves.line_ids\ + moves.line_ids\ .filtered(lambda x: x.account_id == self.company_data['default_account_receivable'])\ .reconcile() - self.assertTrue(res.get('partials')) - exchange_diff = res['partials'].exchange_move_id + exchange_diff = moves.line_ids.matched_debit_ids.exchange_move_id self.assertTrue(exchange_diff) with self.assertRaises(UserError), self.cr.savepoint(): exchange_diff.button_draft() diff --git a/addons/account/tests/test_account_move_reconcile.py b/addons/account/tests/test_account_move_reconcile.py index 3f05e4e2344..eda6a267d07 100644 --- a/addons/account/tests/test_account_move_reconcile.py +++ b/addons/account/tests/test_account_move_reconcile.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- # pylint: disable=C0326 +from contextlib import contextmanager + from odoo.addons.account.tests.common import AccountTestInvoicingCommon from odoo.tests import tagged from odoo.tests.common import Form @@ -199,6 +201,12 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): # during the reconciliation. # ------------------------------------------------------------------------- + def _get_partials(self, amls): + return (amls.matched_debit_ids | amls.matched_credit_ids).sorted() + + def _get_caba_moves(self, moves): + return moves.search([('tax_cash_basis_origin_move_id', 'in', moves.ids)]) + def _create_line_for_reconciliation(self, balance, amount_currency, currency, move_date): move = self.env['account.move'].create({ 'move_type': 'entry', @@ -268,8 +276,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): ): with self.subTest(sub_test_name=sub_test_name): - res = batch.reconcile() - self.assertTrue(res.get('full_reconcile')) + batch.reconcile() + self.assertTrue(batch.full_reconcile_id) self.assertRecordValues( batch, [{'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}] * len(batch), @@ -307,12 +315,12 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line1 = self._create_line_for_reconciliation(*line1_vals) line2 = self._create_line_for_reconciliation(*line2_vals) with self.subTest(line1_vals=line1_vals, line2_vals=line2_vals): - - res = (line1 + line2).reconcile() + (line1 + line2).reconcile() # Reconcile. # Don't check the result since this is already checked by another tests. - exchange_diff = res['partials'].exchange_move_id + partials = self._get_partials(line1 + line2) + exchange_diff = partials.exchange_move_id self.assertTrue(exchange_diff) # Unreconcile. @@ -387,10 +395,12 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(0.0, -0.01, currency_1, '2017-01-01') line_2 = self._create_line_for_reconciliation(0.0, 0.02, currency_2, '2016-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) - self.assertFalse(res['partials']) + self.assertFalse(partials) def test_reconcile_lines_corner_case_2_zero_amount_currency_same_foreign_currency(self): """ Test a corner case when both lines have something to reconcile in company currency but nothing @@ -404,20 +414,31 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(-0.01, 0.0, currency, '2017-01-01') line_2 = self._create_line_for_reconciliation(0.02, 0.0, currency, '2016-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) + full_reconcile = amls.full_reconcile_id - self.assertRecordValues(res['partials'], [{ - 'amount': 0.01, - 'debit_amount_currency': 0.0, - 'credit_amount_currency': 0.0, - 'debit_move_id': line_2.id, - 'credit_move_id': line_1.id, - 'exchange_move_id': None, - }]) - self.assertTrue(res.get('full_reconcile')) - self.assertRecordValues(res['full_reconcile'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['full_reconcile'].exchange_move_id.line_ids, [ + self.assertTrue(full_reconcile) + self.assertRecordValues(partials, [ + { + 'amount': 0.01, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': line_2.id, + 'credit_move_id': line_1.id, + }, + { + 'amount': 0.01, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': line_2.id, + 'credit_move_id': full_reconcile.exchange_move_id.line_ids[0].id, + }, + ]) + self.assertRecordValues(full_reconcile.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(full_reconcile.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 0.01, @@ -433,14 +454,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_expense_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 0.01, - 'debit_amount_currency': 0.0, - 'credit_amount_currency': 0.0, - 'debit_move_id': line_2.id, - 'credit_move_id': res['full_reconcile'].exchange_move_id.line_ids[0].id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) @@ -455,18 +469,38 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(-0.01, -0.01, comp_curr, '2017-01-01') line_2 = self._create_line_for_reconciliation(0.0, 0.03, foreign_curr, '2016-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) + full_reconcile = amls.full_reconcile_id - self.assertRecordValues(res['partials'], [{ - 'amount': 0.0, - 'debit_amount_currency': 0.02, - 'credit_amount_currency': 0.0, - 'debit_move_id': line_2.id, - 'credit_move_id': line_1.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertTrue(full_reconcile) + self.assertRecordValues(partials, [ + { + 'amount': 0.0, + 'debit_amount_currency': 0.02, + 'credit_amount_currency': 0.0, + 'debit_move_id': line_2.id, + 'credit_move_id': line_1.id, + }, + { + 'amount': 0.01, + 'debit_amount_currency': 0.01, + 'credit_amount_currency': 0.01, + 'debit_move_id': partials.exchange_move_id.line_ids[0].id, + 'credit_move_id': line_1.id, + }, + { + 'amount': 0.0, + 'debit_amount_currency': 0.01, + 'credit_amount_currency': 0.01, + 'debit_move_id': line_2.id, + 'credit_move_id': full_reconcile.exchange_move_id.line_ids[0].id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.01, 'credit': 0.0, @@ -482,9 +516,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_income_account.id, }, ]) - self.assertTrue(res.get('full_reconcile')) - self.assertRecordValues(res['full_reconcile'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['full_reconcile'].exchange_move_id.line_ids, [ + self.assertRecordValues(full_reconcile.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(full_reconcile.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 0.0, @@ -500,23 +533,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_expense_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [ - { - 'amount': 0.01, - 'debit_amount_currency': 0.01, - 'credit_amount_currency': 0.01, - 'debit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - 'credit_move_id': line_1.id, - }, - { - 'amount': 0.0, - 'debit_amount_currency': 0.01, - 'credit_amount_currency': 0.01, - 'debit_move_id': line_2.id, - 'credit_move_id': res['full_reconcile'].exchange_move_id.line_ids[0].id, - }, - ]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) @@ -527,10 +544,12 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(-0.01, 0.0, foreign_curr2, '2017-01-01') line_2 = self._create_line_for_reconciliation(0.01, 0.03, foreign_curr1, '2016-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) - self.assertRecordValues(res['partials'], [{ + self.assertRecordValues(partials, [{ 'amount': 0.01, 'debit_amount_currency': 0.03, 'credit_amount_currency': 0.0, @@ -538,28 +557,70 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'credit_move_id': line_1.id, 'exchange_move_id': None, }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) + def test_reconcile_lines_corner_case_5_zero_balance_in_one_line_same_foreign_currency(self): + """ Test a corner case when both lines have something to reconcile in company currency but one has nothing + in foreign currency. It could be due to: + - a bad usage of the `no_exchange_difference` context key + - a partial reconciliation made before migrating to this version + - some rounding error when dealing with currencies having != decimal places + - strange journal items made by the user + """ + currency = self.currency_data['currency'] + + line_1 = self._create_line_for_reconciliation(-0.06, 0.0, currency, '2017-01-01') + line_2 = self._create_line_for_reconciliation(0.12, 0.24, currency, '2017-01-01') + amls = line_1 + line_2 + + amls.reconcile() + partials = self._get_partials(amls) + + self.assertRecordValues(partials, [ + { + 'amount': 0.06, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': line_2.id, + 'credit_move_id': line_1.id, + }, + ]) + self.assertRecordValues(amls, [ + {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, + {'amount_residual': 0.06, 'amount_residual_currency': 0.24, 'reconciled': False}, + ]) + def test_reconcile_exchange_difference_on_partial_same_foreign_currency_debit_expense_partial_payment(self): currency = self.currency_data['currency'] line_1 = self._create_line_for_reconciliation(60.0, 120.0, currency, '2017-01-01') line_2 = self._create_line_for_reconciliation(-80.0, -240.0, currency, '2016-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 120.0, - 'credit_amount_currency': 120.0, - 'debit_move_id': line_1.id, - 'credit_move_id': line_2.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 120.0, + 'credit_amount_currency': 120.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_2.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': line_1.id, + 'credit_move_id': partials.exchange_move_id.line_ids[0].id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, @@ -575,14 +636,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_expense_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 0.0, - 'credit_amount_currency': 0.0, - 'debit_move_id': line_1.id, - 'credit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': -40.0, 'amount_residual_currency': -120.0, 'reconciled': False}, ]) @@ -592,18 +646,29 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(40.0, 120.0, currency, '2016-01-01') line_2 = self._create_line_for_reconciliation(-120.0, -240.0, currency, '2017-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 120.0, - 'credit_amount_currency': 120.0, - 'debit_move_id': line_1.id, - 'credit_move_id': line_2.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 120.0, + 'credit_amount_currency': 120.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_2.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': partials.exchange_move_id.line_ids[0].id, + 'credit_move_id': line_2.id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, @@ -619,14 +684,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_income_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 0.0, - 'credit_amount_currency': 0.0, - 'debit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - 'credit_move_id': line_2.id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': -60.0, 'amount_residual_currency': -120.0, 'reconciled': False}, ]) @@ -636,18 +694,29 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(-40.0, -120.0, currency, '2016-01-01') line_2 = self._create_line_for_reconciliation(120.0, 240.0, currency, '2017-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 120.0, - 'credit_amount_currency': 120.0, - 'debit_move_id': line_2.id, - 'credit_move_id': line_1.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 120.0, + 'credit_amount_currency': 120.0, + 'debit_move_id': line_2.id, + 'credit_move_id': line_1.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': line_2.id, + 'credit_move_id': partials.exchange_move_id.line_ids[0].id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, @@ -663,14 +732,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_expense_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 0.0, - 'credit_amount_currency': 0.0, - 'debit_move_id': line_2.id, - 'credit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 60.0, 'amount_residual_currency': 120.0, 'reconciled': False}, ]) @@ -680,18 +742,29 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(-60.0, -120.0, currency, '2017-01-01') line_2 = self._create_line_for_reconciliation(80.0, 240.0, currency, '2016-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 120.0, - 'credit_amount_currency': 120.0, - 'debit_move_id': line_2.id, - 'credit_move_id': line_1.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 120.0, + 'credit_amount_currency': 120.0, + 'debit_move_id': line_2.id, + 'credit_move_id': line_1.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': partials.exchange_move_id.line_ids[0].id, + 'credit_move_id': line_1.id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, @@ -707,14 +780,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_income_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 0.0, - 'credit_amount_currency': 0.0, - 'debit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - 'credit_move_id': line_1.id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 40.0, 'amount_residual_currency': 120.0, 'reconciled': False}, ]) @@ -725,18 +791,29 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(60.0, 120.0, foreign_curr, '2017-01-01') line_2 = self._create_line_for_reconciliation(-80.0, -80.0, comp_curr, '2016-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 120.0, - 'credit_amount_currency': 40.0, - 'debit_move_id': line_1.id, - 'credit_move_id': line_2.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 120.0, + 'credit_amount_currency': 40.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_2.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': line_1.id, + 'credit_move_id': partials.exchange_move_id.line_ids[0].id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, @@ -752,14 +829,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_expense_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 0.0, - 'credit_amount_currency': 0.0, - 'debit_move_id': line_1.id, - 'credit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': -40.0, 'amount_residual_currency': -40.0, 'reconciled': False}, ]) @@ -770,18 +840,29 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(60.0, 60.0, comp_curr, '2017-01-01') line_2 = self._create_line_for_reconciliation(-80.0, -240.0, foreign_curr, '2016-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 40.0, - 'credit_amount_currency': 120.0, - 'debit_move_id': line_1.id, - 'credit_move_id': line_2.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 40.0, + 'credit_amount_currency': 120.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_2.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 20.0, + 'credit_amount_currency': 20.0, + 'debit_move_id': line_1.id, + 'credit_move_id': partials.exchange_move_id.line_ids[0].id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, @@ -797,14 +878,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_expense_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 20.0, - 'credit_amount_currency': 20.0, - 'debit_move_id': line_1.id, - 'credit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': -40.0, 'amount_residual_currency': -120.0, 'reconciled': False}, ]) @@ -815,18 +889,29 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(40.0, 120.0, foreign_curr, '2016-01-01') line_2 = self._create_line_for_reconciliation(-120.0, -120.0, comp_curr, '2017-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 120.0, - 'credit_amount_currency': 40.0, - 'debit_move_id': line_1.id, - 'credit_move_id': line_2.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 120.0, + 'credit_amount_currency': 40.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_2.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 20.0, + 'credit_amount_currency': 20.0, + 'debit_move_id': partials.exchange_move_id.line_ids[0].id, + 'credit_move_id': line_2.id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, @@ -842,14 +927,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_income_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 20.0, - 'credit_amount_currency': 20.0, - 'debit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - 'credit_move_id': line_2.id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': -60.0, 'amount_residual_currency': -60.0, 'reconciled': False}, ]) @@ -860,18 +938,29 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(40.0, 40.0, comp_curr, '2016-01-01') line_2 = self._create_line_for_reconciliation(-120.0, -240.0, foreign_curr, '2017-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 40.0, - 'credit_amount_currency': 120.0, - 'debit_move_id': line_1.id, - 'credit_move_id': line_2.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 40.0, + 'credit_amount_currency': 120.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_2.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': partials.exchange_move_id.line_ids[0].id, + 'credit_move_id': line_2.id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, @@ -887,14 +976,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_income_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 0.0, - 'credit_amount_currency': 0.0, - 'debit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - 'credit_move_id': line_2.id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': -60.0, 'amount_residual_currency': -120.0, 'reconciled': False}, ]) @@ -905,18 +987,29 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(-40.0, -120.0, foreign_curr, '2016-01-01') line_2 = self._create_line_for_reconciliation(120.0, 120.0, comp_curr, '2017-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 40.0, - 'credit_amount_currency': 120.0, - 'debit_move_id': line_2.id, - 'credit_move_id': line_1.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 40.0, + 'credit_amount_currency': 120.0, + 'debit_move_id': line_2.id, + 'credit_move_id': line_1.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 20.0, + 'credit_amount_currency': 20.0, + 'debit_move_id': line_2.id, + 'credit_move_id': partials.exchange_move_id.line_ids[0].id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, @@ -932,14 +1025,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_expense_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 20.0, - 'credit_amount_currency': 20.0, - 'debit_move_id': line_2.id, - 'credit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 60.0, 'amount_residual_currency': 60.0, 'reconciled': False}, ]) @@ -950,18 +1036,29 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(-40.0, -40.0, comp_curr, '2016-01-01') line_2 = self._create_line_for_reconciliation(120.0, 240.0, foreign_curr, '2017-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 120.0, - 'credit_amount_currency': 40.0, - 'debit_move_id': line_2.id, - 'credit_move_id': line_1.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 120.0, + 'credit_amount_currency': 40.0, + 'debit_move_id': line_2.id, + 'credit_move_id': line_1.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': line_2.id, + 'credit_move_id': partials.exchange_move_id.line_ids[0].id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, @@ -977,14 +1074,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_expense_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 0.0, - 'credit_amount_currency': 0.0, - 'debit_move_id': line_2.id, - 'credit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 60.0, 'amount_residual_currency': 120.0, 'reconciled': False}, ]) @@ -995,18 +1085,29 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(-60.0, -60.0, comp_curr, '2017-01-01') line_2 = self._create_line_for_reconciliation(80.0, 240.0, foreign_curr, '2016-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 120.0, - 'credit_amount_currency': 40.0, - 'debit_move_id': line_2.id, - 'credit_move_id': line_1.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 120.0, + 'credit_amount_currency': 40.0, + 'debit_move_id': line_2.id, + 'credit_move_id': line_1.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 20.0, + 'credit_amount_currency': 20.0, + 'debit_move_id': partials.exchange_move_id.line_ids[0].id, + 'credit_move_id': line_1.id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, @@ -1022,14 +1123,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_income_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 20.0, - 'credit_amount_currency': 20.0, - 'debit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - 'credit_move_id': line_1.id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 40.0, 'amount_residual_currency': 120.0, 'reconciled': False}, ]) @@ -1040,18 +1134,29 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(-60.0, -120.0, foreign_curr, '2017-01-01') line_2 = self._create_line_for_reconciliation(80.0, 80.0, comp_curr, '2016-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 40.0, - 'credit_amount_currency': 120.0, - 'debit_move_id': line_2.id, - 'credit_move_id': line_1.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 40.0, + 'credit_amount_currency': 120.0, + 'debit_move_id': line_2.id, + 'credit_move_id': line_1.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': partials.exchange_move_id.line_ids[0].id, + 'credit_move_id': line_1.id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, @@ -1067,14 +1172,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_income_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 0.0, - 'credit_amount_currency': 0.0, - 'debit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - 'credit_move_id': line_1.id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 40.0, 'amount_residual_currency': 40.0, 'reconciled': False}, ]) @@ -1084,20 +1182,32 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(60.0, 120.0, currency, '2017-01-01') line_2 = self._create_line_for_reconciliation(-40.0, -120.0, currency, '2016-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) + full_reconcile = amls.full_reconcile_id - self.assertTrue(res.get('full_reconcile')) - self.assertRecordValues(res['full_reconcile'], [{'exchange_move_id': False}]) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 120.0, - 'credit_amount_currency': 120.0, - 'debit_move_id': line_1.id, - 'credit_move_id': line_2.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertTrue(full_reconcile) + self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 120.0, + 'credit_amount_currency': 120.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_2.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': line_1.id, + 'credit_move_id': partials.exchange_move_id.line_ids[0].id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, @@ -1113,14 +1223,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_expense_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 0.0, - 'credit_amount_currency': 0.0, - 'debit_move_id': line_1.id, - 'credit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) @@ -1130,20 +1233,32 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(40.0, 120.0, currency, '2016-01-01') line_2 = self._create_line_for_reconciliation(-60.0, -120.0, currency, '2017-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) + full_reconcile = amls.full_reconcile_id - self.assertTrue(res.get('full_reconcile')) - self.assertRecordValues(res['full_reconcile'], [{'exchange_move_id': False}]) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 120.0, - 'credit_amount_currency': 120.0, - 'debit_move_id': line_1.id, - 'credit_move_id': line_2.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertTrue(full_reconcile) + self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 120.0, + 'credit_amount_currency': 120.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_2.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': partials.exchange_move_id.line_ids[0].id, + 'credit_move_id': line_2.id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, @@ -1159,14 +1274,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_income_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 0.0, - 'credit_amount_currency': 0.0, - 'debit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - 'credit_move_id': line_2.id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) @@ -1176,20 +1284,32 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(-40.0, -120.0, currency, '2016-01-01') line_2 = self._create_line_for_reconciliation(60.0, 120.0, currency, '2017-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) + full_reconcile = amls.full_reconcile_id - self.assertTrue(res.get('full_reconcile')) - self.assertRecordValues(res['full_reconcile'], [{'exchange_move_id': False}]) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 120.0, - 'credit_amount_currency': 120.0, - 'debit_move_id': line_2.id, - 'credit_move_id': line_1.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertTrue(full_reconcile) + self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 120.0, + 'credit_amount_currency': 120.0, + 'debit_move_id': line_2.id, + 'credit_move_id': line_1.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': line_2.id, + 'credit_move_id': partials.exchange_move_id.line_ids[0].id, + } + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, @@ -1205,14 +1325,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_expense_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 0.0, - 'credit_amount_currency': 0.0, - 'debit_move_id': line_2.id, - 'credit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) @@ -1222,20 +1335,32 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(-60.0, -120.0, currency, '2017-01-01') line_2 = self._create_line_for_reconciliation(40.0, 120.0, currency, '2016-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) + full_reconcile = amls.full_reconcile_id - self.assertTrue(res.get('full_reconcile')) - self.assertRecordValues(res['full_reconcile'], [{'exchange_move_id': False}]) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 120.0, - 'credit_amount_currency': 120.0, - 'debit_move_id': line_2.id, - 'credit_move_id': line_1.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertTrue(full_reconcile) + self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 120.0, + 'credit_amount_currency': 120.0, + 'debit_move_id': line_2.id, + 'credit_move_id': line_1.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': partials.exchange_move_id.line_ids[0].id, + 'credit_move_id': line_1.id, + } + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, @@ -1251,13 +1376,6 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_income_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 0.0, - 'credit_amount_currency': 0.0, - 'debit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - 'credit_move_id': line_1.id, - }]) self.assertRecordValues(line_1 + line_2, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, @@ -1269,20 +1387,32 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(60.0, 120.0, foreign_curr, '2017-01-01') line_2 = self._create_line_for_reconciliation(-40.0, -40.0, comp_curr, '2016-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) + full_reconcile = amls.full_reconcile_id - self.assertTrue(res.get('full_reconcile')) - self.assertRecordValues(res['full_reconcile'], [{'exchange_move_id': False}]) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 120.0, - 'credit_amount_currency': 40.0, - 'debit_move_id': line_1.id, - 'credit_move_id': line_2.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertTrue(full_reconcile) + self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 120.0, + 'credit_amount_currency': 40.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_2.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': line_1.id, + 'credit_move_id': partials.exchange_move_id.line_ids[0].id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, @@ -1298,14 +1428,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_expense_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 0.0, - 'credit_amount_currency': 0.0, - 'debit_move_id': line_1.id, - 'credit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) @@ -1316,20 +1439,32 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(60.0, 60.0, comp_curr, '2017-01-01') line_2 = self._create_line_for_reconciliation(-40.0, -120.0, foreign_curr, '2016-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) + full_reconcile = amls.full_reconcile_id - self.assertTrue(res.get('full_reconcile')) - self.assertRecordValues(res['full_reconcile'], [{'exchange_move_id': False}]) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 40.0, - 'credit_amount_currency': 120.0, - 'debit_move_id': line_1.id, - 'credit_move_id': line_2.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertTrue(full_reconcile) + self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 40.0, + 'credit_amount_currency': 120.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_2.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 20.0, + 'credit_amount_currency': 20.0, + 'debit_move_id': line_1.id, + 'credit_move_id': partials.exchange_move_id.line_ids[0].id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, @@ -1345,14 +1480,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_expense_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 20.0, - 'credit_amount_currency': 20.0, - 'debit_move_id': line_1.id, - 'credit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) @@ -1363,20 +1491,32 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(40.0, 120.0, foreign_curr, '2016-01-01') line_2 = self._create_line_for_reconciliation(-60.0, -60.0, comp_curr, '2017-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) + full_reconcile = amls.full_reconcile_id - self.assertTrue(res.get('full_reconcile')) - self.assertRecordValues(res['full_reconcile'], [{'exchange_move_id': False}]) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 120.0, - 'credit_amount_currency': 40.0, - 'debit_move_id': line_1.id, - 'credit_move_id': line_2.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertTrue(full_reconcile) + self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 120.0, + 'credit_amount_currency': 40.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_2.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 20.0, + 'credit_amount_currency': 20.0, + 'debit_move_id': partials.exchange_move_id.line_ids[0].id, + 'credit_move_id': line_2.id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, @@ -1392,14 +1532,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_income_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 20.0, - 'credit_amount_currency': 20.0, - 'debit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - 'credit_move_id': line_2.id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) @@ -1410,20 +1543,32 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(40.0, 40.0, comp_curr, '2016-01-01') line_2 = self._create_line_for_reconciliation(-60.0, -120.0, foreign_curr, '2017-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) + full_reconcile = amls.full_reconcile_id - self.assertTrue(res.get('full_reconcile')) - self.assertRecordValues(res['full_reconcile'], [{'exchange_move_id': False}]) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 40.0, - 'credit_amount_currency': 120.0, - 'debit_move_id': line_1.id, - 'credit_move_id': line_2.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertTrue(full_reconcile) + self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 40.0, + 'credit_amount_currency': 120.0, + 'debit_move_id': line_1.id, + 'credit_move_id': line_2.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': partials.exchange_move_id.line_ids[0].id, + 'credit_move_id': line_2.id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, @@ -1439,14 +1584,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_income_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 0.0, - 'credit_amount_currency': 0.0, - 'debit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - 'credit_move_id': line_2.id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) @@ -1457,20 +1595,32 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(-40.0, -40.0, comp_curr, '2016-01-01') line_2 = self._create_line_for_reconciliation(60.0, 120.0, foreign_curr, '2017-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) + full_reconcile = amls.full_reconcile_id - self.assertTrue(res.get('full_reconcile')) - self.assertRecordValues(res['full_reconcile'], [{'exchange_move_id': False}]) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 120.0, - 'credit_amount_currency': 40.0, - 'debit_move_id': line_2.id, - 'credit_move_id': line_1.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertTrue(full_reconcile) + self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 120.0, + 'credit_amount_currency': 40.0, + 'debit_move_id': line_2.id, + 'credit_move_id': line_1.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': line_2.id, + 'credit_move_id': partials.exchange_move_id.line_ids[0].id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, @@ -1486,14 +1636,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_expense_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 0.0, - 'credit_amount_currency': 0.0, - 'debit_move_id': line_2.id, - 'credit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) @@ -1504,20 +1647,32 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(-40.0, -120.0, foreign_curr, '2016-01-01') line_2 = self._create_line_for_reconciliation(60.0, 60.0, comp_curr, '2017-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) + full_reconcile = amls.full_reconcile_id - self.assertTrue(res.get('full_reconcile')) - self.assertRecordValues(res['full_reconcile'], [{'exchange_move_id': False}]) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 40.0, - 'credit_amount_currency': 120.0, - 'debit_move_id': line_2.id, - 'credit_move_id': line_1.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertTrue(full_reconcile) + self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 40.0, + 'credit_amount_currency': 120.0, + 'debit_move_id': line_2.id, + 'credit_move_id': line_1.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 20.0, + 'credit_amount_currency': 20.0, + 'debit_move_id': line_2.id, + 'credit_move_id': partials.exchange_move_id.line_ids[0].id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.0, 'credit': 20.0, @@ -1533,14 +1688,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_expense_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 20.0, - 'credit_amount_currency': 20.0, - 'debit_move_id': line_2.id, - 'credit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) @@ -1551,20 +1699,32 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(-60.0, -60.0, comp_curr, '2017-01-01') line_2 = self._create_line_for_reconciliation(40.0, 120.0, foreign_curr, '2016-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) + full_reconcile = amls.full_reconcile_id - self.assertTrue(res.get('full_reconcile')) - self.assertRecordValues(res['full_reconcile'], [{'exchange_move_id': False}]) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 120.0, - 'credit_amount_currency': 40.0, - 'debit_move_id': line_2.id, - 'credit_move_id': line_1.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertTrue(full_reconcile) + self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 120.0, + 'credit_amount_currency': 40.0, + 'debit_move_id': line_2.id, + 'credit_move_id': line_1.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 20.0, + 'credit_amount_currency': 20.0, + 'debit_move_id': partials.exchange_move_id.line_ids[0].id, + 'credit_move_id': line_1.id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, @@ -1580,14 +1740,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_income_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 20.0, - 'credit_amount_currency': 20.0, - 'debit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - 'credit_move_id': line_1.id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) @@ -1598,20 +1751,32 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): line_1 = self._create_line_for_reconciliation(-60.0, -120.0, foreign_curr, '2017-01-01') line_2 = self._create_line_for_reconciliation(40.0, 40.0, comp_curr, '2016-01-01') + amls = line_1 + line_2 - res = (line_1 + line_2).reconcile() + amls.reconcile() + partials = self._get_partials(amls) + full_reconcile = amls.full_reconcile_id - self.assertTrue(res.get('full_reconcile')) - self.assertRecordValues(res['full_reconcile'], [{'exchange_move_id': False}]) - self.assertRecordValues(res['partials'], [{ - 'amount': 40.0, - 'debit_amount_currency': 40.0, - 'credit_amount_currency': 120.0, - 'debit_move_id': line_2.id, - 'credit_move_id': line_1.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertTrue(full_reconcile) + self.assertRecordValues(full_reconcile, [{'exchange_move_id': False}]) + self.assertRecordValues(partials, [ + { + 'amount': 40.0, + 'debit_amount_currency': 40.0, + 'credit_amount_currency': 120.0, + 'debit_move_id': line_2.id, + 'credit_move_id': line_1.id, + }, + { + 'amount': 20.0, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': partials.exchange_move_id.line_ids[0].id, + 'credit_move_id': line_1.id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2017-01-01')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 20.0, 'credit': 0.0, @@ -1627,14 +1792,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_income_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 20.0, - 'debit_amount_currency': 0.0, - 'credit_amount_currency': 0.0, - 'debit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - 'credit_move_id': line_1.id, - }]) - self.assertRecordValues(line_1 + line_2, [ + self.assertRecordValues(amls, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) @@ -1794,9 +1952,10 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'amount_residual': 839.40, 'amount_residual_currency': 839.40, 'reconciled': False}, ]) - res = (refund1_rec_line + inv1_rec_line).reconcile() + (refund1_rec_line + inv1_rec_line).reconcile() + partials = self._get_partials(refund1_rec_line + inv1_rec_line) - self.assertRecordValues(res['partials'], [{ + self.assertRecordValues(partials, [{ 'amount': 839.4, 'debit_amount_currency': 839.4, 'credit_amount_currency': 839.4, @@ -1821,17 +1980,28 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'amount_residual': 36740.69, 'amount_residual_currency': 1935.72, 'reconciled': False}, ]) - res = (refund1_rec_line + inv2_rec_line).reconcile() + partials = self._get_partials(refund1_rec_line + inv2_rec_line) + (refund1_rec_line + inv2_rec_line).reconcile() + partials = self._get_partials(refund1_rec_line + inv2_rec_line) - partials - self.assertRecordValues(res['partials'], [{ - 'amount': 540.18, - 'debit_amount_currency': 28.46, - 'credit_amount_currency': 540.18, - 'debit_move_id': inv2_rec_line.id, - 'credit_move_id': refund1_rec_line.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2019-06-24')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertRecordValues(partials, [ + { + 'amount': 540.18, + 'debit_amount_currency': 28.46, + 'credit_amount_currency': 540.18, + 'debit_move_id': inv2_rec_line.id, + 'credit_move_id': refund1_rec_line.id, + }, + { + 'amount': 6.34, + 'debit_amount_currency': 6.34, + 'credit_amount_currency': 6.34, + 'debit_move_id': partials.exchange_move_id.line_ids[0].id, + 'credit_move_id': refund1_rec_line.id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2019-06-24')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 6.34, 'credit': 0.0, @@ -1847,13 +2017,6 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_income_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 6.34, - 'debit_amount_currency': 6.34, - 'credit_amount_currency': 6.34, - 'debit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - 'credit_move_id': refund1_rec_line.id, - }]) self.assertRecordValues(refund1_rec_line + inv2_rec_line, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 36200.51, 'amount_residual_currency': 1907.26, 'reconciled': False}, @@ -1861,14 +2024,14 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): self.assert_invoice_outstanding_reconciled_widget(refund1, { inv1.id: 839.40, inv2.id: 540.18, - res['partials'].exchange_move_id.id: 6.34, + partials.exchange_move_id.id: 6.34, }) self.assert_invoice_outstanding_to_reconcile_widget(refund1, {}) # 3th reconciliation inv1 + pay1 self.assert_invoice_outstanding_reconciled_widget(inv2, { refund1.id: 28.46, - res['partials'].exchange_move_id.id: 6.34, + partials.exchange_move_id.id: 6.34, }) self.assert_invoice_outstanding_to_reconcile_widget(inv2, { pay1.move_id.id: 1907.17, @@ -1879,17 +2042,28 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'amount_residual': -36511.34, 'amount_residual_currency': -1907.17, 'reconciled': False}, ]) - res = (inv2_rec_line + pay1_rec_line).reconcile() + partials = self._get_partials(inv2_rec_line + pay1_rec_line) + (inv2_rec_line + pay1_rec_line).reconcile() + partials = self._get_partials(inv2_rec_line + pay1_rec_line) - partials - self.assertRecordValues(res['partials'], [{ - 'amount': 36198.80, - 'debit_amount_currency': 1907.17, - 'credit_amount_currency': 1907.17, - 'debit_move_id': inv2_rec_line.id, - 'credit_move_id': pay1_rec_line.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2019-06-28')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertRecordValues(partials, [ + { + 'amount': 36198.80, + 'debit_amount_currency': 1907.17, + 'credit_amount_currency': 1907.17, + 'debit_move_id': inv2_rec_line.id, + 'credit_move_id': pay1_rec_line.id, + }, + { + 'amount': 312.54, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': partials.exchange_move_id.line_ids[0].id, + 'credit_move_id': pay1_rec_line.id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2019-06-28')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 312.54, 'credit': 0.0, @@ -1909,12 +2083,12 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'amount_residual': 1.71, 'amount_residual_currency': 0.09, 'reconciled': False}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) - payment_exchange_id = inv2_rec_line.matched_credit_ids.filtered(lambda x: x.id != res['partials'].id) + payment_exchange_id = inv2_rec_line.matched_credit_ids.filtered(lambda x: x not in partials) self.assert_invoice_outstanding_reconciled_widget(inv2, { refund1.id: 28.46, pay1.move_id.id: 1907.17, - res['partials'].exchange_move_id.id: 312.54, + partials.exchange_move_id.id: 312.54, payment_exchange_id[0].exchange_move_id.id: 6.34, }) @@ -1927,17 +2101,28 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'amount_residual': -1.77, 'amount_residual_currency': -0.09, 'reconciled': False}, ]) - res = (inv2_rec_line + pay2_rec_line).reconcile() + partials = self._get_partials(inv2_rec_line + pay2_rec_line) + (inv2_rec_line + pay2_rec_line).reconcile() + partials = self._get_partials(inv2_rec_line + pay2_rec_line) - partials - self.assertRecordValues(res['partials'], [{ - 'amount': 1.71, - 'debit_amount_currency': 0.09, - 'credit_amount_currency': 0.09, - 'debit_move_id': inv2_rec_line.id, - 'credit_move_id': pay2_rec_line.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2019-09-24')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertRecordValues(partials, [ + { + 'amount': 1.71, + 'debit_amount_currency': 0.09, + 'credit_amount_currency': 0.09, + 'debit_move_id': inv2_rec_line.id, + 'credit_move_id': pay2_rec_line.id, + }, + { + 'amount': 0.06, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': partials.exchange_move_id.line_ids[0].id, + 'credit_move_id': pay2_rec_line.id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2019-09-24')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.06, 'credit': 0.0, @@ -1957,20 +2142,19 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) - payment_exchange_id = inv2_rec_line.matched_credit_ids.filtered(lambda x: x.id != res['partials'].id) + payment_exchange_id = inv2_rec_line.matched_credit_ids.filtered(lambda x: x not in partials) self.assert_invoice_outstanding_reconciled_widget(inv2, { refund1.id: 28.46, pay1.move_id.id: 1907.17, pay2.move_id.id: 0.09, - res['partials'].exchange_move_id.id: 0.06, + partials.exchange_move_id.id: 0.06, payment_exchange_id[0].exchange_move_id.id: 6.34, payment_exchange_id[1].exchange_move_id.id: 312.54, }) self.assert_invoice_outstanding_to_reconcile_widget(inv2, {}) - self.assertTrue(res.get('full_reconcile')) - self.assertRecordValues(res['full_reconcile'], [{'exchange_move_id': None}]) + self.assertRecordValues(inv2_rec_line.full_reconcile_id, [{'exchange_move_id': None}]) def test_reconcile_special_mexican_workflow_2(self): comp_curr = self.company_data['currency'] @@ -2077,9 +2261,10 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'amount_residual': 839.40, 'amount_residual_currency': 839.40, 'reconciled': False}, ]) - res = (refund1_rec_line + inv1_rec_line).reconcile() + (refund1_rec_line + inv1_rec_line).reconcile() + partials = self._get_partials(refund1_rec_line + inv1_rec_line) - self.assertRecordValues(res['partials'], [{ + self.assertRecordValues(partials, [{ 'amount': 839.4, 'debit_amount_currency': 839.4, 'credit_amount_currency': 839.4, @@ -2106,17 +2291,27 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'amount_residual': 36740.69, 'amount_residual_currency': 1935.72, 'reconciled': False}, ]) - res = (inv2_rec_line + pay1_rec_line).reconcile() + (inv2_rec_line + pay1_rec_line).reconcile() + partials = self._get_partials(inv2_rec_line + pay1_rec_line) - self.assertRecordValues(res['partials'], [{ - 'amount': 36198.8, - 'debit_amount_currency': 1907.17, - 'credit_amount_currency': 1907.17, - 'debit_move_id': inv2_rec_line.id, - 'credit_move_id': pay1_rec_line.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2019-06-28')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertRecordValues(partials, [ + { + 'amount': 36198.8, + 'debit_amount_currency': 1907.17, + 'credit_amount_currency': 1907.17, + 'debit_move_id': inv2_rec_line.id, + 'credit_move_id': pay1_rec_line.id, + }, + { + 'amount': 312.54, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': partials.exchange_move_id.line_ids[0].id, + 'credit_move_id': pay1_rec_line.id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2019-06-28')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 312.54, 'credit': 0.0, @@ -2138,7 +2333,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): ]) self.assert_invoice_outstanding_reconciled_widget(inv2, { pay1.move_id.id: 1907.17, - res['partials'].exchange_move_id.id: 312.54, + partials.exchange_move_id.id: 312.54, }) self.assert_invoice_outstanding_to_reconcile_widget(inv2, { refund1.id: 28.46, @@ -2154,17 +2349,28 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'amount_residual': 541.89, 'amount_residual_currency': 28.55, 'reconciled': False}, ]) - res = (refund1_rec_line + inv2_rec_line).reconcile() + partials = self._get_partials(refund1_rec_line + inv2_rec_line) + (refund1_rec_line + inv2_rec_line).reconcile() + partials = self._get_partials(refund1_rec_line + inv2_rec_line) - partials - self.assertRecordValues(res['partials'], [{ - 'amount': 540.18, - 'debit_amount_currency': 28.46, - 'credit_amount_currency': 540.18, - 'debit_move_id': inv2_rec_line.id, - 'credit_move_id': refund1_rec_line.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2019-06-24')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + self.assertRecordValues(partials, [ + { + 'amount': 540.18, + 'debit_amount_currency': 28.46, + 'credit_amount_currency': 540.18, + 'debit_move_id': inv2_rec_line.id, + 'credit_move_id': refund1_rec_line.id, + }, + { + 'amount': 6.34, + 'debit_amount_currency': 6.34, + 'credit_amount_currency': 6.34, + 'debit_move_id': partials.exchange_move_id.line_ids[0].id, + 'credit_move_id': refund1_rec_line.id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2019-06-24')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 6.34, 'credit': 0.0, @@ -2180,13 +2386,6 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'account_id': self.exch_income_account.id, }, ]) - self.assertRecordValues(res['exchange_partials'], [{ - 'amount': 6.34, - 'debit_amount_currency': 6.34, - 'credit_amount_currency': 6.34, - 'debit_move_id': res['partials'].exchange_move_id.line_ids[0].id, - 'credit_move_id': refund1_rec_line.id, - }]) self.assertRecordValues(refund1_rec_line + inv2_rec_line, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 1.71, 'amount_residual_currency': 0.09, 'reconciled': False}, @@ -2194,7 +2393,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): self.assert_invoice_outstanding_reconciled_widget(refund1, { inv1.id: 839.40, inv2.id: 540.18, - res['partials'].exchange_move_id.id: 6.34, + partials.exchange_move_id.id: 6.34, }) self.assert_invoice_outstanding_to_reconcile_widget(refund1, {}) @@ -2202,16 +2401,28 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): pay2.move_id.id: 0.09, }) - res = (inv2_rec_line + pay2_rec_line).reconcile() - self.assertRecordValues(res['partials'], [{ - 'amount': 1.71, - 'debit_amount_currency': 0.09, - 'credit_amount_currency': 0.09, - 'debit_move_id': inv2_rec_line.id, - 'credit_move_id': pay2_rec_line.id, - }]) - self.assertRecordValues(res['partials'].exchange_move_id, [{'date': fields.Date.from_string('2019-09-24')}]) - self.assertRecordValues(res['partials'].exchange_move_id.line_ids, [ + partials = self._get_partials(inv2_rec_line + pay2_rec_line) + (inv2_rec_line + pay2_rec_line).reconcile() + partials = self._get_partials(inv2_rec_line + pay2_rec_line) - partials + + self.assertRecordValues(partials, [ + { + 'amount': 1.71, + 'debit_amount_currency': 0.09, + 'credit_amount_currency': 0.09, + 'debit_move_id': inv2_rec_line.id, + 'credit_move_id': pay2_rec_line.id, + }, + { + 'amount': 0.06, + 'debit_amount_currency': 0.0, + 'credit_amount_currency': 0.0, + 'debit_move_id': partials.exchange_move_id.line_ids[0].id, + 'credit_move_id': pay2_rec_line.id, + }, + ]) + self.assertRecordValues(partials.exchange_move_id, [{'date': fields.Date.from_string('2019-09-24')}]) + self.assertRecordValues(partials.exchange_move_id.line_ids, [ { 'debit': 0.06, 'credit': 0.0, @@ -2231,20 +2442,19 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) - payment_exchange_id = inv2_rec_line.matched_credit_ids.filtered(lambda x: x.id != res['partials'].id) + payment_exchange_id = inv2_rec_line.matched_credit_ids.filtered(lambda x: x not in partials) self.assert_invoice_outstanding_reconciled_widget(inv2, { refund1.id: 28.46, pay1.move_id.id: 1907.17, pay2.move_id.id: 0.09, - res['partials'].exchange_move_id.id: 0.06, + partials.exchange_move_id.id: 0.06, payment_exchange_id[0].exchange_move_id.id: 312.54, payment_exchange_id[1].exchange_move_id.id: 6.34, }) self.assert_invoice_outstanding_to_reconcile_widget(inv2, {}) - self.assertTrue(res.get('full_reconcile')) - self.assertRecordValues(res['full_reconcile'], [{'exchange_move_id': None}]) + self.assertRecordValues(inv2_rec_line.full_reconcile_id, [{'exchange_move_id': None}]) def test_migration_to_new_reconciliation_same_foreign_currency(self): foreign_curr = self.currency_data['currency'] @@ -2357,6 +2567,74 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): self.assertTrue(invoice.payment_state in ('in_payment', 'paid')) + def test_reconcile_plan(self): + @contextmanager + def rollback(): + savepoint = self.cr.savepoint() + yield + savepoint.rollback() + + comp_curr = self.company_data['currency'] + + line_1 = self._create_line_for_reconciliation(600.0, 600.0, comp_curr, '2017-01-01') + line_2 = self._create_line_for_reconciliation(-100.0, -100.0, comp_curr, '2017-01-02') + line_3 = self._create_line_for_reconciliation(700.0, 700.0, comp_curr, '2017-01-03') + line_5 = self._create_line_for_reconciliation(-700.0, -700.0, comp_curr, '2017-01-04') + line_4 = self._create_line_for_reconciliation(-500.0, -500.0, comp_curr, '2017-01-05') + + with rollback(): + # 5 batches of 1 aml. This won't reconcile anything. + self.env['account.move.line']._reconcile_plan([line_1, line_2, line_3, line_4, line_5]) + self.assertFalse(self._get_partials(line_1 + line_2 + line_3 + line_4 + line_5)) + + with rollback(): + # one batch of 5 amls. + self.env['account.move.line']._reconcile_plan([line_1 + line_2 + line_3 + line_4 + line_5]) + self.assertRecordValues( + self._get_partials(line_1 + line_2 + line_3 + line_4 + line_5), + [ + {'amount': 100.0, 'debit_move_id': line_1.id, 'credit_move_id': line_2.id}, + {'amount': 500.0, 'debit_move_id': line_1.id, 'credit_move_id': line_5.id}, + {'amount': 200.0, 'debit_move_id': line_3.id, 'credit_move_id': line_5.id}, + {'amount': 500.0, 'debit_move_id': line_3.id, 'credit_move_id': line_4.id}, + ], + ) + + with rollback(): + # Reconcile line_3 + line_5 and line_1 + line_4. line_2 is alone so will not be reconciled. + self.env['account.move.line']._reconcile_plan([line_3 + line_5, line_1 + line_4, line_2]) + self.assertRecordValues( + self._get_partials(line_1 + line_2 + line_3 + line_4 + line_5), + [ + {'amount': 700.0, 'debit_move_id': line_3.id, 'credit_move_id': line_5.id}, + {'amount': 500.0, 'debit_move_id': line_1.id, 'credit_move_id': line_4.id}, + ], + ) + + with rollback(): + # Reconcile line_3 + line_5 first, then line_1 + line_4, then the remaining amls with line_2. + self.env['account.move.line']._reconcile_plan([[line_3 + line_5, line_1 + line_4, line_2]]) + self.assertRecordValues( + self._get_partials(line_1 + line_2 + line_3 + line_4 + line_5), + [ + {'amount': 700.0, 'debit_move_id': line_3.id, 'credit_move_id': line_5.id}, + {'amount': 500.0, 'debit_move_id': line_1.id, 'credit_move_id': line_4.id}, + {'amount': 100.0, 'debit_move_id': line_1.id, 'credit_move_id': line_2.id}, + ], + ) + + with rollback(): + # Same as the previous test but with a lot of sub-plan to test the result is the same. + self.env['account.move.line']._reconcile_plan([[[line_3 + line_5], [[line_1 + line_4], line_2]]]) + self.assertRecordValues( + self._get_partials(line_1 + line_2 + line_3 + line_4 + line_5), + [ + {'amount': 700.0, 'debit_move_id': line_3.id, 'credit_move_id': line_5.id}, + {'amount': 500.0, 'debit_move_id': line_1.id, 'credit_move_id': line_4.id}, + {'amount': 100.0, 'debit_move_id': line_1.id, 'credit_move_id': line_2.id}, + ], + ) + # ------------------------------------------------------------------------- # Test creation of extra journal entries during the reconciliation to # deal with taxes that are exigible on payment (cash basis). @@ -2455,22 +2733,13 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): receivable_lines_1 = (cash_basis_move + payment_move).line_ids\ .filtered(lambda line: line.account_id == self.extra_receivable_account_1) - res = receivable_lines_1.reconcile() + tax_cash_basis_moves = self._get_caba_moves(receivable_lines_1.move_id) + receivable_lines_1.reconcile() + tax_cash_basis_moves = self._get_caba_moves(receivable_lines_1.move_id) - tax_cash_basis_moves - self.assertFullReconcile(res['full_reconcile'], receivable_lines_1) - self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 2) - self.assertRecordValues(res['tax_cash_basis_moves'][0].line_ids, [ - # Base amount of tax_1 & tax_2: - {'debit': 25.0, 'credit': 0.0, 'account_id': self.cash_basis_base_account.id}, - {'debit': 0.0, 'credit': 25.0, 'account_id': self.cash_basis_base_account.id}, - # tax_1: - {'debit': 8.33, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, - {'debit': 0.0, 'credit': 8.33, 'account_id': self.tax_account_1.id}, - # tax_2: - {'debit': 0.0, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, - {'debit': 0.0, 'credit': 0.0, 'account_id': self.tax_account_2.id}, - ]) - self.assertRecordValues(res['tax_cash_basis_moves'][1].line_ids, [ + self.assertFullReconcile(receivable_lines_1.full_reconcile_id, receivable_lines_1) + self.assertEqual(len(tax_cash_basis_moves), 2) + self.assertRecordValues(tax_cash_basis_moves[0].line_ids, [ # Base amount of tax_1 & tax_2: {'debit': 8.33, 'credit': 0.0, 'account_id': self.cash_basis_base_account.id}, {'debit': 0.0, 'credit': 8.33, 'account_id': self.cash_basis_base_account.id}, @@ -2481,6 +2750,17 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'debit': 0.0, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, {'debit': 0.0, 'credit': 0.0, 'account_id': self.tax_account_2.id}, ]) + self.assertRecordValues(tax_cash_basis_moves[1].line_ids, [ + # Base amount of tax_1 & tax_2: + {'debit': 25.0, 'credit': 0.0, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 25.0, 'account_id': self.cash_basis_base_account.id}, + # tax_1: + {'debit': 8.33, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 8.33, 'account_id': self.tax_account_1.id}, + # tax_2: + {'debit': 0.0, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'account_id': self.tax_account_2.id}, + ]) self.assertAmountsGroupByAccount([ # Account Balance Amount Currency @@ -2496,22 +2776,24 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): receivable_lines_2 = (cash_basis_move + payment_move).line_ids\ .filtered(lambda line: line.account_id == self.extra_receivable_account_2) - res = receivable_lines_2.reconcile() + tax_cash_basis_moves = self._get_caba_moves(receivable_lines_2.move_id) + receivable_lines_2.reconcile() + tax_cash_basis_moves = self._get_caba_moves(receivable_lines_2.move_id) - tax_cash_basis_moves - self.assertFullReconcile(res['full_reconcile'], receivable_lines_2) - self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 3) - self.assertRecordValues(res['tax_cash_basis_moves'][0].line_ids, [ + self.assertFullReconcile(receivable_lines_2.full_reconcile_id, receivable_lines_2) + self.assertEqual(len(tax_cash_basis_moves), 3) + self.assertRecordValues(tax_cash_basis_moves[0].line_ids, [ # Base amount of tax_1 & tax_2: - {'debit': 33.33, 'credit': 0.0, 'account_id': self.cash_basis_base_account.id}, - {'debit': 0.0, 'credit': 33.33, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.01, 'credit': 0.0, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 0.01, 'account_id': self.cash_basis_base_account.id}, # tax_1: - {'debit': 11.11, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, - {'debit': 0.0, 'credit': 11.11, 'account_id': self.tax_account_1.id}, + {'debit': 0.0, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'account_id': self.tax_account_1.id}, # tax_2: {'debit': 0.0, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, {'debit': 0.0, 'credit': 0.0, 'account_id': self.tax_account_2.id}, ]) - self.assertRecordValues(res['tax_cash_basis_moves'][1].line_ids, [ + self.assertRecordValues(tax_cash_basis_moves[1].line_ids, [ # Base amount of tax_1 & tax_2: {'debit': 33.32, 'credit': 0.0, 'account_id': self.cash_basis_base_account.id}, {'debit': 0.0, 'credit': 33.32, 'account_id': self.cash_basis_base_account.id}, @@ -2522,13 +2804,13 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'debit': 0.0, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, {'debit': 0.0, 'credit': 0.0, 'account_id': self.tax_account_2.id}, ]) - self.assertRecordValues(res['tax_cash_basis_moves'][2].line_ids, [ + self.assertRecordValues(tax_cash_basis_moves[2].line_ids, [ # Base amount of tax_1 & tax_2: - {'debit': 0.01, 'credit': 0.0, 'account_id': self.cash_basis_base_account.id}, - {'debit': 0.0, 'credit': 0.01, 'account_id': self.cash_basis_base_account.id}, + {'debit': 33.33, 'credit': 0.0, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 33.33, 'account_id': self.cash_basis_base_account.id}, # tax_1: - {'debit': 0.0, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, - {'debit': 0.0, 'credit': 0.0, 'account_id': self.tax_account_1.id}, + {'debit': 11.11, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 11.11, 'account_id': self.tax_account_1.id}, # tax_2: {'debit': 0.0, 'credit': 0.0, 'account_id': self.cash_basis_transfer_account.id}, {'debit': 0.0, 'credit': 0.0, 'account_id': self.tax_account_2.id}, @@ -2546,11 +2828,13 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): payable_lines_1 = (cash_basis_move + payment_move).line_ids\ .filtered(lambda line: line.account_id == self.extra_payable_account_1) - res = payable_lines_1.reconcile() + tax_cash_basis_moves = self._get_caba_moves(payable_lines_1.move_id) + payable_lines_1.reconcile() + tax_cash_basis_moves = self._get_caba_moves(payable_lines_1.move_id) - tax_cash_basis_moves - self.assertFullReconcile(res['full_reconcile'], payable_lines_1) - self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 1) - self.assertRecordValues(res['tax_cash_basis_moves'].line_ids, [ + self.assertFullReconcile(payable_lines_1.full_reconcile_id, payable_lines_1) + self.assertEqual(len(tax_cash_basis_moves), 1) + self.assertRecordValues(tax_cash_basis_moves.line_ids, [ # Base amount of tax_1 & tax_2: {'debit': 0.01, 'credit': 0.0, 'account_id': self.cash_basis_base_account.id}, {'debit': 0.0, 'credit': 0.01, 'account_id': self.cash_basis_base_account.id}, @@ -2562,7 +2846,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'debit': 0.0, 'credit': 0.0, 'account_id': self.tax_account_2.id}, ]) - self.assertRecordValues(res['full_reconcile'].exchange_move_id.line_ids, [ + self.assertRecordValues(payable_lines_1.full_reconcile_id.exchange_move_id.line_ids, [ {'account_id': self.tax_account_2.id, 'debit': 0.0, 'credit': 0.01, 'tax_ids': [], 'tax_line_id': self.cash_basis_tax_tiny_amount.id}, {'account_id': self.cash_basis_transfer_account.id, 'debit': 0.01, 'credit': 0.0, 'tax_ids': [], 'tax_line_id': False}, ]) @@ -2676,22 +2960,12 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): receivable_lines_1 = (cash_basis_move + payment_move).line_ids\ .filtered(lambda line: line.account_id == self.extra_receivable_account_1) - res = receivable_lines_1.reconcile() + receivable_lines_1.reconcile() + tax_cash_basis_moves = self._get_caba_moves(receivable_lines_1.move_id) - self.assertFullReconcileAccount(res['full_reconcile'], self.extra_receivable_account_1) - self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 2) - self.assertRecordValues(res['tax_cash_basis_moves'][0].line_ids, [ - # Base amount of tax_1 & tax_2: - {'debit': 12.5, 'credit': 0.0, 'amount_currency': 25.0, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, - {'debit': 0.0, 'credit': 12.5, 'amount_currency': -25.0, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, - # tax_1: - {'debit': 4.17, 'credit': 0.0, 'amount_currency': 8.333, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, - {'debit': 0.0, 'credit': 4.17, 'amount_currency': -8.333, 'currency_id': currency_id, 'account_id': self.tax_account_1.id}, - # tax_2: - {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.003, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, - {'debit': 0.0, 'credit': 0.0, 'amount_currency': -0.003, 'currency_id': currency_id, 'account_id': self.tax_account_2.id}, - ]) - self.assertRecordValues(res['tax_cash_basis_moves'][1].line_ids, [ + self.assertFullReconcileAccount(receivable_lines_1.full_reconcile_id, self.extra_receivable_account_1) + self.assertEqual(len(tax_cash_basis_moves), 2) + self.assertRecordValues(tax_cash_basis_moves[0].line_ids, [ # Base amount of tax_1 & tax_2: {'debit': 4.2, 'credit': 0.0, 'amount_currency': 8.331, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, {'debit': 0.0, 'credit': 4.2, 'amount_currency': -8.331, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, @@ -2702,8 +2976,19 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.001, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, {'debit': 0.0, 'credit': 0.0, 'amount_currency': -0.001, 'currency_id': currency_id, 'account_id': self.tax_account_2.id}, ]) + self.assertRecordValues(tax_cash_basis_moves[1].line_ids, [ + # Base amount of tax_1 & tax_2: + {'debit': 12.5, 'credit': 0.0, 'amount_currency': 25.0, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 12.5, 'amount_currency': -25.0, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, + # tax_1: + {'debit': 4.17, 'credit': 0.0, 'amount_currency': 8.333, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 4.17, 'amount_currency': -8.333, 'currency_id': currency_id, 'account_id': self.tax_account_1.id}, + # tax_2: + {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.003, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'amount_currency': -0.003, 'currency_id': currency_id, 'account_id': self.tax_account_2.id}, + ]) - caba_transition_lines_1 = res['tax_cash_basis_moves'].line_ids.filtered(lambda x: x.account_id == self.cash_basis_transfer_account) + caba_transition_lines_1 = tax_cash_basis_moves.line_ids.filtered(lambda x: x.account_id == self.cash_basis_transfer_account) caba_transition_exchange_moves_1 = caba_transition_lines_1.matched_credit_ids.exchange_move_id self.assertEqual(len(caba_transition_exchange_moves_1), 2) self.assertRecordValues(caba_transition_exchange_moves_1[0].line_ids, [ @@ -2729,33 +3014,13 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): receivable_lines_2 = (cash_basis_move + payment_move).line_ids\ .filtered(lambda line: line.account_id == self.extra_receivable_account_2) - res = receivable_lines_2.reconcile() + tax_cash_basis_moves = self._get_caba_moves(receivable_lines_2.move_id) + receivable_lines_2.reconcile() + tax_cash_basis_moves = self._get_caba_moves(receivable_lines_2.move_id) - tax_cash_basis_moves - self.assertFullReconcileAccount(res['full_reconcile'], self.extra_receivable_account_2) - self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 3) - self.assertRecordValues(res['tax_cash_basis_moves'][0].line_ids, [ - # Base amount of tax_1 & tax_2: - {'debit': 16.67, 'credit': 0.0, 'amount_currency': 33.331, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, - {'debit': 0.0, 'credit': 16.67, 'amount_currency': -33.331, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, - # tax_1: - {'debit': 5.56, 'credit': 0.0, 'amount_currency': 11.109, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, - {'debit': 0.0, 'credit': 5.56, 'amount_currency': -11.109, 'currency_id': currency_id, 'account_id': self.tax_account_1.id}, - # tax_2: - {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.003, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, - {'debit': 0.0, 'credit': 0.0, 'amount_currency': -0.003, 'currency_id': currency_id, 'account_id': self.tax_account_2.id}, - ]) - self.assertRecordValues(res['tax_cash_basis_moves'][1].line_ids, [ - # Base amount of tax_1 & tax_2: - {'debit': 16.66, 'credit': 0.0, 'amount_currency': 33.323, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, - {'debit': 0.0, 'credit': 16.66, 'amount_currency': -33.323, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, - # tax_1: - {'debit': 5.55, 'credit': 0.0, 'amount_currency': 11.107, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, - {'debit': 0.0, 'credit': 5.55, 'amount_currency': -11.107, 'currency_id': currency_id, 'account_id': self.tax_account_1.id}, - # tax_2: - {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.003, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, - {'debit': 0.0, 'credit': 0.0, 'amount_currency': -0.003, 'currency_id': currency_id, 'account_id': self.tax_account_2.id}, - ]) - self.assertRecordValues(res['tax_cash_basis_moves'][2].line_ids, [ + self.assertFullReconcileAccount(receivable_lines_2.full_reconcile_id, self.extra_receivable_account_2) + self.assertEqual(len(tax_cash_basis_moves), 3) + self.assertRecordValues(tax_cash_basis_moves[0].line_ids, [ # Base amount of tax_1 & tax_2: {'debit': 0.01, 'credit': 0.0, 'amount_currency': 0.007, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, {'debit': 0.0, 'credit': 0.01, 'amount_currency': -0.007, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, @@ -2766,22 +3031,44 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.tax_account_2.id}, ]) + self.assertRecordValues(tax_cash_basis_moves[1].line_ids, [ + # Base amount of tax_1 & tax_2: + {'debit': 16.66, 'credit': 0.0, 'amount_currency': 33.323, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 16.66, 'amount_currency': -33.323, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, + # tax_1: + {'debit': 5.55, 'credit': 0.0, 'amount_currency': 11.107, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 5.55, 'amount_currency': -11.107, 'currency_id': currency_id, 'account_id': self.tax_account_1.id}, + # tax_2: + {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.003, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'amount_currency': -0.003, 'currency_id': currency_id, 'account_id': self.tax_account_2.id}, + ]) + self.assertRecordValues(tax_cash_basis_moves[2].line_ids, [ + # Base amount of tax_1 & tax_2: + {'debit': 16.67, 'credit': 0.0, 'amount_currency': 33.331, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, + {'debit': 0.0, 'credit': 16.67, 'amount_currency': -33.331, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, + # tax_1: + {'debit': 5.56, 'credit': 0.0, 'amount_currency': 11.109, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 5.56, 'amount_currency': -11.109, 'currency_id': currency_id, 'account_id': self.tax_account_1.id}, + # tax_2: + {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.003, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.0, 'amount_currency': -0.003, 'currency_id': currency_id, 'account_id': self.tax_account_2.id}, + ]) - caba_transition_lines_2 = res['tax_cash_basis_moves'].line_ids.filtered(lambda x: x.account_id == self.cash_basis_transfer_account) + caba_transition_lines_2 = tax_cash_basis_moves.line_ids.filtered(lambda x: x.account_id == self.cash_basis_transfer_account) caba_transition_exchange_moves_2 = caba_transition_lines_2.matched_credit_ids.exchange_move_id self.assertEqual(len(caba_transition_exchange_moves_2), 3) self.assertRecordValues(caba_transition_exchange_moves_2[0].line_ids, [ + {'debit': 0.01, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, + {'debit': 0.0, 'credit': 0.01, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.env.company.income_currency_exchange_account_id.id}, + ]) + self.assertRecordValues(caba_transition_exchange_moves_2[1].line_ids, [ {'debit': 0.0, 'credit': 1.86, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, {'debit': 1.86, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.env.company.expense_currency_exchange_account_id.id}, ]) - self.assertRecordValues(caba_transition_exchange_moves_2[1].line_ids, [ + self.assertRecordValues(caba_transition_exchange_moves_2[2].line_ids, [ {'debit': 0.0, 'credit': 1.85, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, {'debit': 1.85, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.env.company.expense_currency_exchange_account_id.id}, ]) - self.assertRecordValues(caba_transition_exchange_moves_2[2].line_ids, [ - {'debit': 0.01, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.cash_basis_transfer_account.id}, - {'debit': 0.0, 'credit': 0.01, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.env.company.income_currency_exchange_account_id.id}, - ]) self.assertAmountsGroupByAccount([ # Account Balance Amount Currency @@ -2795,11 +3082,13 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): payable_lines_1 = (cash_basis_move + payment_move).line_ids\ .filtered(lambda line: line.account_id == self.extra_payable_account_1) - res = payable_lines_1.reconcile() + tax_cash_basis_moves = self._get_caba_moves(payable_lines_1.move_id) + payable_lines_1.reconcile() + tax_cash_basis_moves = self._get_caba_moves(payable_lines_1.move_id) - tax_cash_basis_moves - self.assertFullReconcile(res['full_reconcile'], payable_lines_1) - self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 1) - self.assertRecordValues(res['tax_cash_basis_moves'].line_ids, [ + self.assertFullReconcile(payable_lines_1.full_reconcile_id, payable_lines_1) + self.assertEqual(len(tax_cash_basis_moves), 1) + self.assertRecordValues(tax_cash_basis_moves.line_ids, [ # Base amount of tax_1 & tax_2: {'debit': 0.01, 'credit': 0.0, 'amount_currency': 0.007, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, {'debit': 0.0, 'credit': 0.01, 'amount_currency': -0.007, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, @@ -2811,13 +3100,13 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): {'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.0, 'currency_id': currency_id, 'account_id': self.tax_account_2.id}, ]) - self.assertRecordValues(res['full_reconcile'].exchange_move_id.line_ids, [ + self.assertRecordValues(payable_lines_1.full_reconcile_id.exchange_move_id.line_ids, [ {'account_id': self.cash_basis_base_account.id, 'debit': 0.0, 'credit': 0.0, 'amount_currency': -0.001, 'tax_ids': taxes.ids, 'tax_line_id': False}, {'account_id': self.cash_basis_base_account.id, 'debit': 0.0, 'credit': 0.0, 'amount_currency': 0.001, 'tax_ids': [], 'tax_line_id': False}, ]) # No exchange move should have been created when reconciling the transition account - caba_transition_lines_3 = res['tax_cash_basis_moves'].line_ids.filtered(lambda x: x.account_id == self.cash_basis_transfer_account) + caba_transition_lines_3 = tax_cash_basis_moves.line_ids.filtered(lambda x: x.account_id == self.cash_basis_transfer_account) self.assertFalse(caba_transition_lines_3.matched_credit_ids.exchange_move_id) self.assertAmountsGroupByAccount([ @@ -2920,10 +3209,11 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): receivable_lines = (cash_basis_move + payment_move).line_ids\ .filtered(lambda line: line.account_id == self.extra_receivable_account_1) - res = receivable_lines.reconcile() + receivable_lines.reconcile() + tax_cash_basis_moves = self._get_caba_moves(receivable_lines.move_id) - self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 1) - self.assertRecordValues(res['tax_cash_basis_moves'].line_ids, [ + self.assertEqual(len(tax_cash_basis_moves), 1) + self.assertRecordValues(tax_cash_basis_moves.line_ids, [ # Base amount: {'debit': 150.0, 'credit': 0.0, 'amount_currency': 300.0, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, {'debit': 0.0, 'credit': 150.0, 'amount_currency': -300.0, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, @@ -2934,10 +3224,10 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): receivable_lines2 = (payment_move + end_move).line_ids\ .filtered(lambda line: line.account_id == self.extra_receivable_account_1) - res = receivable_lines2.reconcile() + receivable_lines2.reconcile() - self.assertTrue(res.get('full_reconcile')) - exchange_diff = res['full_reconcile'].exchange_move_id + self.assertTrue(receivable_lines2.full_reconcile_id) + exchange_diff = receivable_lines2.full_reconcile_id.exchange_move_id caba_rounding_correction = exchange_diff.line_ids\ .filtered(lambda line: line.account_id == self.cash_basis_transfer_account)\ .sorted(lambda line: (line.account_id, line.debit, line.credit)) @@ -3203,15 +3493,16 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): receivable_lines = (cash_basis_move + payment_move).line_ids\ .filtered(lambda line: line.account_id == self.extra_receivable_account_1) - res = receivable_lines.reconcile() + receivable_lines.reconcile() + tax_cash_basis_moves = self._get_caba_moves(receivable_lines.move_id) - self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 1) + self.assertEqual(len(tax_cash_basis_moves), 1) # Tax values based on payment # Invoice amount 300 (amount currency) with payment rate 2 (400 payment amount divided by 200 invoice balance) # - Base amount: 150 company currency # - Tax amount: 50 company currency - self.assertRecordValues(res['tax_cash_basis_moves'].line_ids, [ + self.assertRecordValues(tax_cash_basis_moves.line_ids, [ # Base amount: {'debit': 150.0, 'credit': 0.0, 'amount_currency': 300.0, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, {'debit': 0.0, 'credit': 150.0, 'amount_currency': -300.0, 'currency_id': currency_id, 'account_id': self.cash_basis_base_account.id}, @@ -3227,7 +3518,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): ]) # Exchange difference - self.assertFalse(res['full_reconcile'].exchange_move_id, "No exchange move difference should be created for the full reconcile object ,as there is no cash basis rounding.") + self.assertFalse(receivable_lines.full_reconcile_id.exchange_move_id, "No exchange move difference should be created for the full reconcile object ,as there is no cash basis rounding.") def test_reconcile_cash_basis_refund_multicurrency(self): self.env.company.tax_exigibility = True @@ -3268,7 +3559,9 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): invoice.action_post() refund.action_post() - (refund + invoice).line_ids.filtered(lambda x: x.account_id.account_type == 'asset_receivable').reconcile() + (refund + invoice).line_ids\ + .filtered(lambda x: x.account_id.account_type == 'asset_receivable')\ + .reconcile() # Check the cash basis moves self.assertRecordValues( @@ -3430,17 +3723,18 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): receivable_lines = (invoice_move + payment_move).line_ids\ .filtered(lambda line: line.account_id == self.extra_receivable_account_1) - res = receivable_lines.reconcile() + receivable_lines.reconcile() + tax_cash_basis_moves = self._get_caba_moves(receivable_lines.move_id) # == Check reconciliation of invoice with payment == - self.assertFullReconcile(res['full_reconcile'], receivable_lines) - self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 1) + self.assertFullReconcile(receivable_lines.full_reconcile_id, receivable_lines) + self.assertEqual(len(tax_cash_basis_moves), 1) # == Check the reconciliation of invoice with tax cash basis journal entry. # /!\ We make the assumption the tax cash basis journal entry is well created. - tax_cash_basis_move = res['tax_cash_basis_moves'] + tax_cash_basis_move = tax_cash_basis_moves taxes_lines = (invoice_move.line_ids + tax_cash_basis_move.line_ids.filtered('debit'))\ .filtered(lambda line: line.account_id == self.cash_basis_transfer_account) @@ -3453,7 +3747,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): tax_cash_basis_move_reverse = tax_cash_basis_move._reverse_moves(cancel=True) - self.assertFullReconcile(res['full_reconcile'], receivable_lines) + self.assertFullReconcile(receivable_lines.full_reconcile_id, receivable_lines) # == Check the reconciliation of the tax cash basis journal entry with its reverse == @@ -3515,12 +3809,13 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): receivable_lines = (invoice_move + refund_move).line_ids\ .filtered(lambda line: line.account_id == self.extra_receivable_account_1) - res = receivable_lines.reconcile() + receivable_lines.reconcile() + tax_cash_basis_moves = self._get_caba_moves(receivable_lines.move_id) - self.assertFullReconcile(res['full_reconcile'], receivable_lines) - self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 2) + self.assertFullReconcile(receivable_lines.full_reconcile_id, receivable_lines) + self.assertEqual(len(tax_cash_basis_moves), 2) - tax_cash_basis_moves = res['tax_cash_basis_moves'].sorted(lambda move: move.tax_cash_basis_origin_move_id.id) + tax_cash_basis_moves = tax_cash_basis_moves.sorted(lambda move: move.tax_cash_basis_origin_move_id.id) # Invoice: cb_lines = tax_cash_basis_moves[0].line_ids.sorted(lambda line: (-abs(line.balance), -line.debit, line.account_id)) @@ -3650,12 +3945,13 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): receivable_lines = (invoice_move + payment_move).line_ids\ .filtered(lambda line: line.account_id == self.extra_receivable_account_1) - res = receivable_lines.reconcile() + receivable_lines.reconcile() + tax_cash_basis_moves = self._get_caba_moves(receivable_lines.move_id) - self.assertFullReconcile(res['full_reconcile'], receivable_lines) - self.assertEqual(len(res.get('tax_cash_basis_moves', [])), 1) + self.assertFullReconcile(receivable_lines.full_reconcile_id, receivable_lines) + self.assertEqual(len(tax_cash_basis_moves), 1) - self.assertRecordValues(res['tax_cash_basis_moves'].line_ids, [ + self.assertRecordValues(tax_cash_basis_moves.line_ids, [ # Base amount x 2 because there is two taxes: {'debit': 100.0, 'credit': 0.0, 'tax_ids': [], 'tax_tag_ids': [], 'account_id': self.cash_basis_base_account.id}, {'debit': 0.0, 'credit': 100.0, 'tax_ids': base_taxes.ids, 'tax_tag_ids': base_tags.ids, 'account_id': self.cash_basis_base_account.id}, diff --git a/addons/account_edi/models/account_move.py b/addons/account_edi/models/account_move.py index 8d462578b5a..6996f1d15ef 100644 --- a/addons/account_edi/models/account_move.py +++ b/addons/account_edi/models/account_move.py @@ -471,16 +471,26 @@ class AccountMoveLine(models.Model): } return res - def reconcile(self): - # OVERRIDE + def _reconcile_pre_hook(self): + # EXTENDS 'account' # In some countries, the payments must be sent to the government under some condition. One of them could be # there is at least one reconciled invoice to the payment. Then, we need to update the state of the edi # documents during the reconciliation. + results = super()._reconcile_pre_hook() all_lines = self + self.matched_debit_ids.debit_move_id + self.matched_credit_ids.credit_move_id - payments = all_lines.move_id.filtered(lambda move: move.payment_id or move.statement_line_id) + results['edi_payments'] = all_lines.move_id\ + .filtered(lambda move: move.payment_id or move.statement_line_id) + results['edi_invoices_per_payment_before'] = { + pay: pay._get_reconciled_invoices() + for pay in results['edi_payments'] + } + return results - invoices_per_payment_before = {pay: pay._get_reconciled_invoices() for pay in payments} - res = super().reconcile() + def _reconcile_post_hook(self, data): + # EXTENDS 'account' + super()._reconcile_post_hook(data) + payments = data['edi_payments'] + invoices_per_payment_before = data['edi_invoices_per_payment_before'] invoices_per_payment_after = {pay: pay._get_reconciled_invoices() for pay in payments} changed_payments = self.env['account.move'] @@ -491,8 +501,6 @@ class AccountMoveLine(models.Model): changed_payments |= payment changed_payments._update_payments_edi_documents() - return res - def remove_move_reconcile(self): # OVERRIDE # When a payment has been sent to the government, it usually contains some information about reconciled diff --git a/addons/purchase_stock/tests/test_stockvaluation.py b/addons/purchase_stock/tests/test_stockvaluation.py index d6d10d7efef..287155e8535 100644 --- a/addons/purchase_stock/tests/test_stockvaluation.py +++ b/addons/purchase_stock/tests/test_stockvaluation.py @@ -1104,11 +1104,11 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon): {'balance': 15.0, 'amount_currency': 30.0, 'account_id': self.stock_input_account.id}, {'balance': -15.0, 'amount_currency': -30.0, 'account_id': self.company_data['default_account_payable'].id}, ]) - self.assertRecordValues(inv.line_ids.full_reconcile_id.reconciled_line_ids, [ + self.assertRecordValues(inv.line_ids.full_reconcile_id.reconciled_line_ids.sorted('balance'), [ # pylint: disable=C0326 {'balance': -42.86, 'amount_currency': -30.0, 'account_id': self.stock_input_account.id}, - {'balance': 27.86, 'amount_currency': 0.0, 'account_id': self.stock_input_account.id}, {'balance': 15.0, 'amount_currency': 30.0, 'account_id': self.stock_input_account.id}, + {'balance': 27.86, 'amount_currency': 0.0, 'account_id': self.stock_input_account.id}, ]) def test_average_realtime_with_two_delivery_anglo_saxon_valuation_multicurrency_different_dates(self):