diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 77a2b02c7c9..d7d69b46379 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -3941,15 +3941,7 @@ class AccountMove(models.Model): :param has_tax: If any taxes are involved in the lines of the invoice :return: a list of tuples containing the lock dates affecting this move, ordered chronologically. """ - locks = [] - user_lock_date = self.company_id._get_user_fiscal_lock_date() - if invoice_date and user_lock_date and invoice_date <= user_lock_date: - locks.append((user_lock_date, _('user'))) - tax_lock_date = self.company_id.tax_lock_date - if invoice_date and tax_lock_date and has_tax and invoice_date <= tax_lock_date: - locks.append((tax_lock_date, _('tax'))) - locks.sort() - return locks + return self.company_id._get_violated_lock_dates(invoice_date, has_tax) @api.model def _move_dict_to_preview_vals(self, move_vals, currency_id=None): diff --git a/addons/account/models/account_move_line.py b/addons/account/models/account_move_line.py index 457392048ec..00161ddb7d0 100644 --- a/addons/account/models/account_move_line.py +++ b/addons/account/models/account_move_line.py @@ -1595,30 +1595,95 @@ class AccountMoveLine(models.Model): # RECONCILIATION # ------------------------------------------------------------------------- + def _get_reconciliation_aml_field_value(self, field, shadowed_aml_values): + self.ensure_one() + if shadowed_aml_values and field in shadowed_aml_values.get(self, {}): + return shadowed_aml_values[self][field] + else: + return self[field] + @api.model - 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_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_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 _prepare_move_line_residual_amounts(self, aml_values, counterpart_currency, shadowed_aml_values=None): + """ Prepare the available residual amounts for each currency. + :param aml_values: The values of account.move.line to consider. + :param counterpart_currency: The currency of the opposite line this line will be reconciled with. + :param shadowed_aml_values: A mapping aml -> dictionary to replace some original aml values to something else. + This is usefull if you want to preview the reconciliation before doing some changes + on amls like changing a date or an account. + :return: A mapping currency -> dictionary containing: + * residual: The residual amount left for this currency. + * rate: The rate applied regarding the company's currency. """ 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 = aml.date + exchange_rate_date = aml._get_reconciliation_aml_field_value('date', shadowed_aml_values) return currency._get_conversion_rate(aml.company_currency_id, currency, aml.company_id, exchange_rate_date) - 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) + def get_accounting_rate(aml, currency): + balance = aml._get_reconciliation_aml_field_value('balance', shadowed_aml_values) + amount_currency = aml._get_reconciliation_aml_field_value('amount_currency', shadowed_aml_values) + if not aml.company_currency_id.is_zero(balance) and not currency.is_zero(amount_currency): + return abs(amount_currency / balance) + aml = aml_values['aml'] + remaining_amount_curr = aml_values['amount_residual_currency'] + remaining_amount = aml_values['amount_residual'] + company_currency = aml.company_currency_id + currency = aml._get_reconciliation_aml_field_value('currency_id', shadowed_aml_values) + account = aml._get_reconciliation_aml_field_value('account_id', shadowed_aml_values) + has_zero_residual = company_currency.is_zero(remaining_amount) + has_zero_residual_currency = currency.is_zero(remaining_amount_curr) + is_rec_pay_account = account.account_type in ('asset_receivable', 'liability_payable') + + available_residual_per_currency = {} + + if not has_zero_residual: + available_residual_per_currency[company_currency] = { + 'residual': remaining_amount, + 'rate': 1, + } + if currency != company_currency and not has_zero_residual_currency: + available_residual_per_currency[currency] = { + 'residual': remaining_amount_curr, + 'rate': get_accounting_rate(aml, currency), + } + + if currency == company_currency \ + and is_rec_pay_account \ + and not has_zero_residual \ + and counterpart_currency != company_currency: + rate = get_odoo_rate(aml, counterpart_currency) + available_residual_per_currency[counterpart_currency] = { + 'residual': counterpart_currency.round(remaining_amount * rate), + 'rate': rate, + } + elif currency == counterpart_currency \ + and currency != company_currency \ + and not has_zero_residual_currency: + available_residual_per_currency[counterpart_currency] = { + 'residual': remaining_amount_curr, + 'rate': get_accounting_rate(aml, currency), + } + return available_residual_per_currency + + @api.model + def _prepare_reconciliation_single_partial(self, debit_values, credit_values, shadowed_aml_values=None): + """ 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_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. + :param shadowed_aml_values: A mapping aml -> dictionary to replace some original aml values to something else. + This is usefull if you want to preview the reconciliation before doing some changes + on amls like changing a date or an account. + :return: A dictionary: + * 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. + """ # ==== 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. @@ -1626,95 +1691,70 @@ class AccountMoveLine(models.Model): 'debit_values': debit_values, 'credit_values': credit_values, } + debit_aml = debit_values['aml'] + credit_aml = credit_values['aml'] + debit_currency = debit_aml._get_reconciliation_aml_field_value('currency_id', shadowed_aml_values) + credit_currency = credit_aml._get_reconciliation_aml_field_value('currency_id', shadowed_aml_values) + company_currency = debit_aml.company_currency_id + 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'] - 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_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') + debit_available_residual_amounts = self._prepare_move_line_residual_amounts( + debit_values, + credit_currency, + shadowed_aml_values=shadowed_aml_values, + ) + credit_available_residual_amounts = self._prepare_move_line_residual_amounts( + credit_values, + debit_currency, + shadowed_aml_values=shadowed_aml_values, + ) - 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. - recon_currency = company_currency - debit_rate = credit_rate = 1.0 - recon_debit_amount = remaining_debit_amount - recon_credit_amount = -remaining_credit_amount - elif debit_aml.currency_id == company_currency \ - and is_rec_pay_account \ - and not has_debit_zero_residual \ - 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_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_aml.currency_id != company_currency \ - and is_rec_pay_account \ - and not has_debit_zero_residual_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_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_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_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_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. - # In that case, we don't want a rate for the opposite line because the exchange difference is supposed - # to reduce only the amount in company currency but not the foreign one. - recon_currency = company_currency - debit_rate = None - credit_rate = None - recon_debit_amount = remaining_debit_amount - recon_credit_amount = -remaining_credit_amount + if debit_currency != company_currency \ + and debit_currency in debit_available_residual_amounts \ + and debit_currency in credit_available_residual_amounts: + recon_currency = debit_currency + elif credit_currency != company_currency \ + and credit_currency in debit_available_residual_amounts \ + and credit_currency in credit_available_residual_amounts: + recon_currency = credit_currency 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_aml) - credit_rate = get_accounting_rate(credit_aml) - recon_debit_amount = remaining_debit_amount - recon_credit_amount = -remaining_credit_amount + + debit_recon_values = debit_available_residual_amounts.get(recon_currency) + credit_recon_values = credit_available_residual_amounts.get(recon_currency) # 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): + if not debit_recon_values: res['debit_values'] = None skip_reconciliation = True - if recon_currency.is_zero(recon_credit_amount): + if not credit_recon_values: res['credit_values'] = None skip_reconciliation = True if skip_reconciliation: return res + recon_debit_amount = debit_recon_values['residual'] + recon_credit_amount = -credit_recon_values['residual'] + # ==== Match both lines together and compute amounts to reconcile ==== + # 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. + # In that case, we don't want a rate for the opposite line because the exchange difference is supposed + # to reduce only the amount in company currency but not the foreign one. + exchange_line_mode = \ + recon_currency == company_currency \ + and debit_currency == credit_currency \ + and ( + not debit_available_residual_amounts.get(debit_currency) + or not credit_available_residual_amounts.get(credit_currency) + ) + # Determine which line is fully matched by the other. compare_amounts = recon_currency.compare_amounts(recon_debit_amount, recon_credit_amount) min_recon_amount = min(recon_debit_amount, recon_credit_amount) @@ -1723,24 +1763,38 @@ class AccountMoveLine(models.Model): # ==== Computation of partial amounts ==== if recon_currency == company_currency: + if exchange_line_mode: + debit_rate = None + credit_rate = None + else: + debit_rate = debit_available_residual_amounts.get(debit_currency, {}).get('rate') + credit_rate = credit_available_residual_amounts.get(credit_currency, {}).get('rate') + # Compute the partial amount expressed in company currency. partial_amount = min_recon_amount # Compute the partial amount expressed in foreign currency. if debit_rate: - partial_debit_amount_currency = debit_aml.currency_id.round(debit_rate * min_recon_amount) + partial_debit_amount_currency = debit_currency.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_aml.currency_id.round(credit_rate * min_recon_amount) + partial_credit_amount_currency = credit_currency.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 else: # recon_currency != company_currency - # Compute the partial amount expressed in company currency. + if exchange_line_mode: + debit_rate = None + credit_rate = None + else: + debit_rate = debit_recon_values['rate'] + credit_rate = credit_recon_values['rate'] + + # Compute the partial amount expressed in foreign currency. if debit_rate: partial_debit_amount = company_currency.round(min_recon_amount / debit_rate) partial_debit_amount = min(partial_debit_amount, remaining_debit_amount) @@ -1756,11 +1810,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_aml.currency_id == company_currency: + if debit_currency == company_currency: partial_debit_amount_currency = partial_amount else: partial_debit_amount_currency = min_recon_amount - if credit_aml.currency_id == company_currency: + if credit_currency == company_currency: partial_credit_amount_currency = partial_amount else: partial_credit_amount_currency = min_recon_amount @@ -1773,13 +1827,13 @@ 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_aml.currency_id.is_zero(debit_exchange_amount): + if not debit_currency.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_aml.currency_id.is_zero(credit_exchange_amount): + if not credit_currency.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 @@ -1792,7 +1846,7 @@ class AccountMoveLine(models.Model): exchange_lines_to_fix += debit_aml amounts_list.append({'amount_residual': debit_exchange_amount}) remaining_debit_amount -= debit_exchange_amount - if debit_aml.currency_id == company_currency: + if debit_currency == company_currency: remaining_debit_amount_curr -= debit_exchange_amount else: # Create an exchange difference ensuring the rate between the residual amounts expressed in @@ -1803,7 +1857,7 @@ class AccountMoveLine(models.Model): exchange_lines_to_fix += debit_aml amounts_list.append({'amount_residual': debit_exchange_amount}) remaining_debit_amount -= debit_exchange_amount - if debit_aml.currency_id == company_currency: + if debit_currency == company_currency: remaining_debit_amount_curr -= debit_exchange_amount if credit_fully_matched: @@ -1813,7 +1867,7 @@ class AccountMoveLine(models.Model): exchange_lines_to_fix += credit_aml amounts_list.append({'amount_residual': credit_exchange_amount}) remaining_credit_amount += credit_exchange_amount - if credit_aml.currency_id == company_currency: + if credit_currency == company_currency: remaining_credit_amount_curr -= credit_exchange_amount else: # Create an exchange difference ensuring the rate between the residual amounts expressed in @@ -1824,13 +1878,16 @@ class AccountMoveLine(models.Model): exchange_lines_to_fix += credit_aml amounts_list.append({'amount_residual': credit_exchange_amount}) remaining_credit_amount -= credit_exchange_amount - if credit_aml.currency_id == company_currency: + if credit_currency == company_currency: remaining_credit_amount_curr -= credit_exchange_amount if exchange_lines_to_fix: res['exchange_values'] = exchange_lines_to_fix._prepare_exchange_difference_move_vals( amounts_list, - exchange_date=max(debit_aml.date, credit_aml.date), + exchange_date=max( + debit_aml._get_reconciliation_aml_field_value('date', shadowed_aml_values), + credit_aml._get_reconciliation_aml_field_value('date', shadowed_aml_values), + ), ) # ==== Create partials ==== @@ -1860,16 +1917,30 @@ class AccountMoveLine(models.Model): return res @api.model - def _prepare_reconciliation_amls(self, values_list): + def _prepare_reconciliation_amls(self, values_list, shadowed_aml_values=None): """ 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. + :param values_list: A list of dictionaries, one for each aml. + :param shadowed_aml_values: A mapping aml -> dictionary to replace some original aml values to something else. + This is usefull if you want to preview the reconciliation before doing some changes + on amls like changing a date or an account. :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_list = iter([ + x + for x in values_list + if x['aml']._get_reconciliation_aml_field_value('balance', shadowed_aml_values) > 0.0 + or x['aml']._get_reconciliation_aml_field_value('amount_currency', shadowed_aml_values) > 0.0 + ]) + credit_values_list = iter([ + x + for x in values_list + if x['aml']._get_reconciliation_aml_field_value('balance', shadowed_aml_values) < 0.0 + or x['aml']._get_reconciliation_aml_field_value('amount_currency', shadowed_aml_values) < 0.0 + ]) debit_values = None credit_values = None fully_reconciled_aml_ids = set() @@ -1896,7 +1967,11 @@ class AccountMoveLine(models.Model): # ==== Compute the amounts to reconcile ==== - results = self._prepare_reconciliation_single_partial(debit_values, credit_values) + results = self._prepare_reconciliation_single_partial( + debit_values, + credit_values, + shadowed_aml_values=shadowed_aml_values, + ) if results.get('partial_values'): all_results.append(results) if results['debit_values'] is None: @@ -1909,11 +1984,14 @@ class AccountMoveLine(models.Model): return all_results, fully_reconciled_aml_ids @api.model - def _prepare_reconciliation_plan(self, plan, amls_values_map): + def _prepare_reconciliation_plan(self, plan, amls_values_map, shadowed_aml_values=None): """ 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 + :param shadowed_aml_values: A mapping aml -> dictionary to replace some original aml values to something else. + This is usefull if you want to preview the reconciliation before doing some changes + on amls like changing a date or an account. :return: A list of all results returned by the '_prepare_reconciliation_amls' method. """ all_fully_reconciled_aml_ids = set() @@ -1921,10 +1999,13 @@ class AccountMoveLine(models.Model): 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 - ]) + amls_results, fully_reconciled_aml_ids = self._prepare_reconciliation_amls( + [ + amls_values_map[aml] + for aml in remaining_amls + ], + shadowed_aml_values=shadowed_aml_values, + ) all_fully_reconciled_aml_ids.update(fully_reconciled_aml_ids) for amls_result in amls_results: all_results.append(amls_result) @@ -1940,8 +2021,39 @@ class AccountMoveLine(models.Model): process_leaf(plan) return all_results + def _check_amls_exigibility_for_reconciliation(self, shadowed_aml_values=None): + """ Ensure the current journal items are eligible to be reconciled together. + :param shadowed_aml_values: A mapping aml -> dictionary to replace some original aml values to something else. + This is usefull if you want to preview the reconciliation before doing some changes + on amls like changing a date or an account. + """ + if not self: + return + + if any(aml.reconciled for aml in self): + raise UserError(_("You are trying to reconcile some entries that are already reconciled.")) + if any(aml.parent_state != 'posted' for aml in self): + raise UserError(_("You can only reconcile posted entries.")) + accounts = self.mapped(lambda x: x._get_reconciliation_aml_field_value('account_id', shadowed_aml_values)) + if len(accounts) > 1: + raise UserError(_( + "Entries are not from the same account: %s", + ", ".join(accounts.mapped('display_name')), + )) + if len(self.company_id) > 1: + raise UserError(_( + "Entries don't belong to the same company: %s", + ", ".join(self.company_id.mapped('display_name')), + )) + if not accounts.reconcile and accounts.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.", + accounts.display_name, + )) + @api.model - def _optimize_reconciliation_plan(self, reconciliation_plan): + def _optimize_reconciliation_plan(self, reconciliation_plan, shadowed_aml_values=None): """ 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. @@ -1955,6 +2067,9 @@ class AccountMoveLine(models.Model): [[account.move.line(1, 2), account.move.line(3, 4)]] :param reconciliation_plan: A list of reconciliation to perform. + :param shadowed_aml_values: A mapping aml -> dictionary to replace some original aml values to something else. + This is usefull if you want to preview the reconciliation before doing some changes + on amls like changing a date or an account. :return: A list of dictionaries containing: * amls: A recordset. * aml_ids: The recordset ids. @@ -1964,18 +2079,19 @@ class AccountMoveLine(models.Model): def process_amls(amls): if self._context.get('reduced_line_sorting'): sorted_amls = amls.sorted(key=lambda aml: ( - aml.date_maturity or aml.date, - aml.currency_id, + aml._get_reconciliation_aml_field_value('date_maturity', shadowed_aml_values) + or aml._get_reconciliation_aml_field_value('date', shadowed_aml_values), + aml._get_reconciliation_aml_field_value('currency_id', shadowed_aml_values), )) else: sorted_amls = amls.sorted(key=lambda aml: ( - aml.date_maturity or aml.date, - aml.currency_id, - aml.amount_currency, - aml.balance, + aml._get_reconciliation_aml_field_value('date_maturity', shadowed_aml_values) + or aml._get_reconciliation_aml_field_value('date', shadowed_aml_values), + aml._get_reconciliation_aml_field_value('currency_id', shadowed_aml_values), + aml._get_reconciliation_aml_field_value('amount_currency', shadowed_aml_values), + aml._get_reconciliation_aml_field_value('balance', shadowed_aml_values), )) - currencies = sorted_amls.currency_id - + currencies = sorted_amls.mapped(lambda x: x._get_reconciliation_aml_field_value('currency_id', shadowed_aml_values)) results = { 'amls': sorted_amls, 'aml_ids': set(sorted_amls.ids), @@ -1984,7 +2100,8 @@ class AccountMoveLine(models.Model): if len(currencies) != 1: nodes = results['nodes'] = [] for currency in currencies: - amls_in_currency = sorted_amls.filtered(lambda x: x.currency_id == currency) + amls_in_currency = sorted_amls\ + .filtered(lambda x: x._get_reconciliation_aml_field_value('currency_id', shadowed_aml_values) == currency) nodes.append({ 'amls': amls_in_currency, 'aml_ids': set(amls_in_currency.ids), @@ -2024,32 +2141,7 @@ class AccountMoveLine(models.Model): # 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 - )) - + amls._check_amls_exigibility_for_reconciliation(shadowed_aml_values=shadowed_aml_values) plan_list.append(plan_node) all_aml_ids.update(plan_node['aml_ids']) @@ -2899,7 +2991,7 @@ class AccountMoveLine(models.Model): def action_open_business_doc(self): return self.move_id.action_open_business_doc() - def action_automatic_entry(self): + def action_automatic_entry(self, default_action=None): action = self.env['ir.actions.act_window']._for_xml_id('account.account_automatic_entry_wizard_action') # Force the values of the move line in the context to avoid issues ctx = dict(self.env.context) @@ -2907,6 +2999,8 @@ class AccountMoveLine(models.Model): ctx.pop('default_journal_id', None) ctx['active_ids'] = self.ids ctx['active_model'] = 'account.move.line' + if default_action: + ctx['default_action'] = default_action action['context'] = ctx return action diff --git a/addons/account/models/company.py b/addons/account/models/company.py index 5f112eae126..3efe85bc490 100644 --- a/addons/account/models/company.py +++ b/addons/account/models/company.py @@ -366,6 +366,23 @@ class ResCompany(models.Model): lock_date = self.fiscalyear_lock_date or date.min return lock_date + def _get_violated_lock_dates(self, accounting_date, has_tax): + """Get all the lock dates affecting the current accounting_date. + :param accoutiaccounting_dateng_date: The accounting date + :param has_tax: If any taxes are involved in the lines of the invoice + :return: a list of tuples containing the lock dates ordered chronologically. + """ + self.ensure_one() + locks = [] + user_lock_date = self._get_user_fiscal_lock_date() + if accounting_date and user_lock_date and accounting_date <= user_lock_date: + locks.append((user_lock_date, _('user'))) + tax_lock_date = self.tax_lock_date + if accounting_date and tax_lock_date and has_tax and accounting_date <= tax_lock_date: + locks.append((tax_lock_date, _('tax'))) + locks.sort() + return locks + def write(self, values): #restrict the closing of FY if there are still unposted entries self._validate_fiscalyear_lock(values) diff --git a/addons/account/tests/common.py b/addons/account/tests/common.py index 5cd34288323..e210b7dce65 100644 --- a/addons/account/tests/common.py +++ b/addons/account/tests/common.py @@ -413,6 +413,41 @@ class AccountTestInvoicingCommon(TransactionCase): return rslt + def create_line_for_reconciliation(self, balance, amount_currency, currency, move_date, account_1=None, partner=None): + write_off_account_to_be_reconciled = account_1 if account_1 else self.receivable_account + move = self.env['account.move'].create({ + 'move_type': 'entry', + 'date': move_date, + 'line_ids': [ + Command.create({ + 'debit': balance if balance > 0.0 else 0.0, + 'credit': -balance if balance < 0.0 else 0.0, + 'amount_currency': amount_currency, + 'account_id': write_off_account_to_be_reconciled.id, + 'currency_id': currency.id, + 'partner_id': partner.id if partner else None, + }), + Command.create({ + 'debit': -balance if balance < 0.0 else 0.0, + 'credit': balance if balance > 0.0 else 0.0, + 'amount_currency': -amount_currency, + 'account_id': self.revenue_account.id, + 'currency_id': currency.id, + 'partner_id': partner.id if partner else None, + }), + ], + }) + move.action_post() + line = move.line_ids.filtered(lambda x: x.account_id == write_off_account_to_be_reconciled) + + self.assertRecordValues(line, [{ + 'amount_residual': balance, + 'amount_residual_currency': amount_currency, + 'reconciled': False, + }]) + + return line + def assertInvoiceValues(self, move, expected_lines_values, expected_move_values): def sort_lines(lines): return lines.sorted(lambda line: (line.sequence, not bool(line.tax_line_id), line.name or '', line.balance)) @@ -541,168 +576,3 @@ class AccountTestInvoicingCommon(TransactionCase): class AccountTestInvoicingHttpCommon(AccountTestInvoicingCommon, HttpCase): pass - - -class TestAccountReconciliationCommon(AccountTestInvoicingCommon): - - """Tests for reconciliation (account.tax) - - Test used to check that when doing a sale or purchase invoice in a different currency, - the result will be balanced. - """ - - @classmethod - def setUpClass(cls, chart_template_ref=None): - super().setUpClass(chart_template_ref=chart_template_ref) - - cls.company = cls.company_data['company'] - cls.company.currency_id = cls.env.ref('base.EUR') - - cls.partner_agrolait = cls.env['res.partner'].create({ - 'name': 'Deco Addict', - 'is_company': True, - 'country_id': cls.env.ref('base.us').id, - }) - cls.partner_agrolait_id = cls.partner_agrolait.id - cls.currency_swiss_id = cls.env.ref("base.CHF").id - cls.currency_usd_id = cls.env.ref("base.USD").id - cls.currency_euro_id = cls.env.ref("base.EUR").id - cls.account_rcv = cls.company_data['default_account_receivable'] - cls.account_rsa = cls.company_data['default_account_payable'] - cls.product = cls.env['product.product'].create({ - 'name': 'Product Product 4', - 'standard_price': 500.0, - 'list_price': 750.0, - 'type': 'consu', - 'categ_id': cls.env.ref('product.product_category_all').id, - }) - - cls.bank_journal_euro = cls.env['account.journal'].create({'name': 'Bank', 'type': 'bank', 'code': 'BNK67'}) - cls.account_euro = cls.bank_journal_euro.default_account_id - - cls.bank_journal_usd = cls.env['account.journal'].create({'name': 'Bank US', 'type': 'bank', 'code': 'BNK68', 'currency_id': cls.currency_usd_id}) - cls.account_usd = cls.bank_journal_usd.default_account_id - - cls.fx_journal = cls.company.currency_exchange_journal_id - cls.diff_income_account = cls.company.income_currency_exchange_account_id - cls.diff_expense_account = cls.company.expense_currency_exchange_account_id - - cls.expense_account = cls.company_data['default_account_expense'] - # cash basis intermediary account - cls.tax_waiting_account = cls.env['account.account'].create({ - 'name': 'TAX_WAIT', - 'code': 'TWAIT', - 'account_type': 'liability_current', - 'reconcile': True, - 'company_id': cls.company.id, - }) - # cash basis final account - cls.tax_final_account = cls.env['account.account'].create({ - 'name': 'TAX_TO_DEDUCT', - 'code': 'TDEDUCT', - 'account_type': 'asset_current', - 'company_id': cls.company.id, - }) - cls.tax_base_amount_account = cls.env['account.account'].create({ - 'name': 'TAX_BASE', - 'code': 'TBASE', - 'account_type': 'asset_current', - 'company_id': cls.company.id, - }) - cls.company.account_cash_basis_base_account_id = cls.tax_base_amount_account.id - - - # Journals - cls.purchase_journal = cls.company_data['default_journal_purchase'] - cls.cash_basis_journal = cls.env['account.journal'].create({ - 'name': 'Test CABA', - 'code': 'tCABA', - 'type': 'general', - }) - cls.general_journal = cls.company_data['default_journal_misc'] - - # Tax Cash Basis - cls.tax_cash_basis = cls.env['account.tax'].create({ - 'name': 'cash basis 20%', - 'type_tax_use': 'purchase', - 'company_id': cls.company.id, - 'country_id': cls.company.account_fiscal_country_id.id, - 'amount': 20, - 'tax_exigibility': 'on_payment', - 'cash_basis_transition_account_id': cls.tax_waiting_account.id, - 'invoice_repartition_line_ids': [ - (0,0, { - 'repartition_type': 'base', - }), - - (0,0, { - 'repartition_type': 'tax', - 'account_id': cls.tax_final_account.id, - }), - ], - 'refund_repartition_line_ids': [ - (0,0, { - 'repartition_type': 'base', - }), - - (0,0, { - 'repartition_type': 'tax', - 'account_id': cls.tax_final_account.id, - }), - ], - }) - cls.env['res.currency.rate'].create([ - { - 'currency_id': cls.env.ref('base.EUR').id, - 'name': '2010-01-02', - 'rate': 1.0, - }, { - 'currency_id': cls.env.ref('base.USD').id, - 'name': '2010-01-02', - 'rate': 1.2834, - }, { - 'currency_id': cls.env.ref('base.USD').id, - 'name': time.strftime('%Y-06-05'), - 'rate': 1.5289, - } - ]) - - def _create_invoice(self, move_type='out_invoice', invoice_amount=50, currency_id=None, partner_id=None, date_invoice=None, payment_term_id=False, auto_validate=False): - date_invoice = date_invoice or time.strftime('%Y') + '-07-01' - - invoice_vals = { - 'move_type': move_type, - 'partner_id': partner_id or self.partner_agrolait_id, - 'invoice_date': date_invoice, - 'date': date_invoice, - 'invoice_line_ids': [(0, 0, { - 'name': 'product that cost %s' % invoice_amount, - 'quantity': 1, - 'price_unit': invoice_amount, - 'tax_ids': [Command.set([])], - })] - } - - if payment_term_id: - invoice_vals['invoice_payment_term_id'] = payment_term_id - - if currency_id: - invoice_vals['currency_id'] = currency_id - - invoice = self.env['account.move'].with_context(default_move_type=move_type).create(invoice_vals) - if auto_validate: - invoice.action_post() - return invoice - - def create_invoice(self, move_type='out_invoice', invoice_amount=50, currency_id=None): - return self._create_invoice(move_type=move_type, invoice_amount=invoice_amount, currency_id=currency_id, auto_validate=True) - - def create_invoice_partner(self, move_type='out_invoice', invoice_amount=50, currency_id=None, partner_id=False, payment_term_id=False): - return self._create_invoice( - move_type=move_type, - invoice_amount=invoice_amount, - currency_id=currency_id, - partner_id=partner_id, - payment_term_id=payment_term_id, - auto_validate=True - ) diff --git a/addons/account/tests/test_account_move_reconcile.py b/addons/account/tests/test_account_move_reconcile.py index eda6a267d07..80b5230e517 100644 --- a/addons/account/tests/test_account_move_reconcile.py +++ b/addons/account/tests/test_account_move_reconcile.py @@ -19,6 +19,10 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): super().setUpClass(chart_template_ref=chart_template_ref) cls.receivable_account = cls.company_data['default_account_receivable'] + cls.payable_account = cls.company_data['default_account_payable'] + cls.expense_account = cls.company_data['default_account_expense'] + cls.revenue_account = cls.company_data['default_account_revenue'] + cls.extra_receivable_account_1 = cls.copy_account(cls.receivable_account) cls.extra_receivable_account_2 = cls.copy_account(cls.receivable_account) cls.extra_payable_account_1 = cls.copy_account(cls.company_data['default_account_payable']) @@ -27,6 +31,7 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): cls.exch_income_account = cls.env.company.income_currency_exchange_account_id cls.exch_expense_account = cls.env.company.expense_currency_exchange_account_id + # ==== Multi-currency setup ==== cls.currency_data_2 = cls.setup_multi_currency_data(default_values={ @@ -207,38 +212,6 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 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', - 'date': move_date, - 'line_ids': [ - Command.create({ - 'debit': balance if balance > 0.0 else 0.0, - 'credit': -balance if balance < 0.0 else 0.0, - 'amount_currency': amount_currency, - 'account_id': self.receivable_account.id, - 'currency_id': currency.id, - }), - Command.create({ - 'debit': -balance if balance < 0.0 else 0.0, - 'credit': balance if balance > 0.0 else 0.0, - 'amount_currency': -amount_currency, - 'account_id': self.company_data['default_account_revenue'].id, - 'currency_id': currency.id, - }), - ], - }) - move.action_post() - line = move.line_ids.filtered(lambda x: x.account_id == self.receivable_account) - - self.assertRecordValues(line, [{ - 'amount_residual': balance, - 'amount_residual_currency': amount_currency, - 'reconciled': False, - }]) - - return line - def test_full_reconcile_bunch_lines(self): """ Test the reconciliation with multiple lines at the same time and ensure the result is always a full reconcile whatever the number of involved currencies. @@ -247,25 +220,25 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): foreign_curr1 = self.currency_data['currency'] foreign_curr2 = self.currency_data_2['currency'] - line_1 = self._create_line_for_reconciliation(1000.0, 1000.0, comp_curr, '2016-01-01') - line_2 = self._create_line_for_reconciliation(-300.0, -300.0, comp_curr, '2016-01-01') - line_3 = self._create_line_for_reconciliation(-400.0, -400.0, comp_curr, '2016-01-01') - line_4 = self._create_line_for_reconciliation(-500.0, -500.0, comp_curr, '2016-01-01') - line_5 = self._create_line_for_reconciliation(200.0, 200.0, comp_curr, '2016-01-01') + line_1 = self.create_line_for_reconciliation(1000.0, 1000.0, comp_curr, '2016-01-01') + line_2 = self.create_line_for_reconciliation(-300.0, -300.0, comp_curr, '2016-01-01') + line_3 = self.create_line_for_reconciliation(-400.0, -400.0, comp_curr, '2016-01-01') + line_4 = self.create_line_for_reconciliation(-500.0, -500.0, comp_curr, '2016-01-01') + line_5 = self.create_line_for_reconciliation(200.0, 200.0, comp_curr, '2016-01-01') comp_curr_batch = line_1 + line_2 + line_3 + line_4 + line_5 - line_1 = self._create_line_for_reconciliation(1200.0, 3600.0, foreign_curr1, '2016-01-01') - line_2 = self._create_line_for_reconciliation(-240.0, -480.0, foreign_curr1, '2017-01-01') - line_3 = self._create_line_for_reconciliation(-720.0, -1440.0, foreign_curr1, '2017-01-01') - line_4 = self._create_line_for_reconciliation(-1020.0, -2040.0, foreign_curr1, '2017-01-01') - line_5 = self._create_line_for_reconciliation(120.0, 360.0, foreign_curr1, '2016-01-01') + line_1 = self.create_line_for_reconciliation(1200.0, 3600.0, foreign_curr1, '2016-01-01') + line_2 = self.create_line_for_reconciliation(-240.0, -480.0, foreign_curr1, '2017-01-01') + line_3 = self.create_line_for_reconciliation(-720.0, -1440.0, foreign_curr1, '2017-01-01') + line_4 = self.create_line_for_reconciliation(-1020.0, -2040.0, foreign_curr1, '2017-01-01') + line_5 = self.create_line_for_reconciliation(120.0, 360.0, foreign_curr1, '2016-01-01') same_curr_batch = line_1 + line_2 + line_3 + line_4 + line_5 - line_1 = self._create_line_for_reconciliation(1200.0, 3600.0, foreign_curr1, '2016-01-01') - line_2 = self._create_line_for_reconciliation(780.0, 2340.0, foreign_curr1, '2016-01-01') - line_3 = self._create_line_for_reconciliation(-240.0, -960.0, foreign_curr2, '2017-01-01') - line_4 = self._create_line_for_reconciliation(-720.0, -2880.0, foreign_curr2, '2017-01-01') - line_5 = self._create_line_for_reconciliation(-1020.0, -4080.0, foreign_curr2, '2017-01-01') + line_1 = self.create_line_for_reconciliation(1200.0, 3600.0, foreign_curr1, '2016-01-01') + line_2 = self.create_line_for_reconciliation(780.0, 2340.0, foreign_curr1, '2016-01-01') + line_3 = self.create_line_for_reconciliation(-240.0, -960.0, foreign_curr2, '2017-01-01') + line_4 = self.create_line_for_reconciliation(-720.0, -2880.0, foreign_curr2, '2017-01-01') + line_5 = self.create_line_for_reconciliation(-1020.0, -4080.0, foreign_curr2, '2017-01-01') multi_curr_batch = line_1 + line_2 + line_3 + line_4 + line_5 for batch, sub_test_name in ( @@ -292,9 +265,9 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): for rate2 in rates: for rate3 in rates: with self.subTest(rate1=rate1, rate2=rate2, rate3=rate3): - line_1 = self._create_line_for_reconciliation(120.0 * rate1, 120.0, currency, '2017-01-01') - line_2 = self._create_line_for_reconciliation(120.0 * rate2, 120.0, currency, '2017-01-01') - line_3 = self._create_line_for_reconciliation(-240.0 * rate3, -240.0, currency, '2017-01-01') + line_1 = self.create_line_for_reconciliation(120.0 * rate1, 120.0, currency, '2017-01-01') + line_2 = self.create_line_for_reconciliation(120.0 * rate2, 120.0, currency, '2017-01-01') + line_3 = self.create_line_for_reconciliation(-240.0 * rate3, -240.0, currency, '2017-01-01') (line_1 + line_2 + line_3).reconcile() self.assertTrue(line_1.full_reconcile_id) @@ -312,8 +285,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): ((60.0, 60.0, comp_curr, '2017-01-01'), (-40.0, -120.0, foreign_curr, '2016-01-01')), ((-60.0, -60.0, comp_curr, '2017-01-01'), (40.0, 120.0, foreign_curr, '2016-01-01')), ): - line1 = self._create_line_for_reconciliation(*line1_vals) - line2 = self._create_line_for_reconciliation(*line2_vals) + 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): (line1 + line2).reconcile() @@ -368,13 +341,13 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): an additional move is added with the right foreign currency amount. """ currency = self.currency_data['currency'] - line_1 = self._create_line_for_reconciliation(0.0, -0.02, currency, '2017-01-01') - line_2 = self._create_line_for_reconciliation(0.0, 0.01, currency, '2017-01-01') + line_1 = self.create_line_for_reconciliation(0.0, -0.02, currency, '2017-01-01') + line_2 = self.create_line_for_reconciliation(0.0, 0.01, currency, '2017-01-01') (line_1 + line_2).reconcile() self.assertFalse(line_1.full_reconcile_id + line_2.full_reconcile_id, "The reconciliation should not be considered full, as 0.01 still remain open in foreign currency.") - line_3 = self._create_line_for_reconciliation(0.0, 0.01, currency, '2017-01-01') + line_3 = self.create_line_for_reconciliation(0.0, 0.01, currency, '2017-01-01') (line_1 + line_3).reconcile() self.assertTrue(line_1.full_reconcile_id) @@ -393,8 +366,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 'currency_subunit_label': 'Bretonnian Denier', })['currency'] - 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') + 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 amls.reconcile() @@ -412,8 +385,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): """ currency = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -467,8 +440,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -542,8 +515,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): foreign_curr1 = self.currency_data['currency'] foreign_curr2 = self.currency_data_2['currency'] - 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') + 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 amls.reconcile() @@ -572,8 +545,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): """ 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') + 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() @@ -596,8 +569,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 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') + 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 amls.reconcile() @@ -644,8 +617,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): def test_reconcile_exchange_difference_on_partial_same_foreign_currency_debit_income_partial_payment(self): currency = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -692,8 +665,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): def test_reconcile_exchange_difference_on_partial_same_foreign_currency_credit_expense_partial_payment(self): currency = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -740,8 +713,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): def test_reconcile_exchange_difference_on_partial_same_foreign_currency_credit_income_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') + 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 amls.reconcile() @@ -789,8 +762,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -838,8 +811,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -887,8 +860,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -936,8 +909,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -985,8 +958,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -1034,8 +1007,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -1083,8 +1056,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -1132,8 +1105,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -1180,8 +1153,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): def test_reconcile_exchange_difference_on_partial_same_foreign_currency_debit_expense_full_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(-40.0, -120.0, currency, '2016-01-01') + 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 amls.reconcile() @@ -1231,8 +1204,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): def test_reconcile_exchange_difference_on_partial_same_foreign_currency_debit_income_full_payment(self): currency = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -1282,8 +1255,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): def test_reconcile_exchange_difference_on_partial_same_foreign_currency_credit_expense_full_payment(self): currency = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -1333,8 +1306,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): def test_reconcile_exchange_difference_on_partial_same_foreign_currency_credit_income_full_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(40.0, 120.0, currency, '2016-01-01') + 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 amls.reconcile() @@ -1385,8 +1358,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -1437,8 +1410,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -1489,8 +1462,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -1541,8 +1514,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -1593,8 +1566,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -1645,8 +1618,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -1697,8 +1670,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -1749,8 +1722,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): comp_curr = self.company_data['currency'] foreign_curr = self.currency_data['currency'] - 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') + 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 amls.reconcile() @@ -2459,8 +2432,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): def test_migration_to_new_reconciliation_same_foreign_currency(self): foreign_curr = self.currency_data['currency'] - 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, 240.0, foreign_curr, '2016-01-01') + 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, 240.0, foreign_curr, '2016-01-01') # Create the partial as it should be created in previous version. self.env['account.partial.reconcile'].create({ @@ -2476,14 +2449,14 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): ]) # Reconcile using the "new" reconciliation. - line_3 = self._create_line_for_reconciliation(-15.0, -30.0, foreign_curr, '2017-01-01') + line_3 = self.create_line_for_reconciliation(-15.0, -30.0, foreign_curr, '2017-01-01') (line_2 + line_3).reconcile() self.assertRecordValues(line_2 + line_3, [ {'amount_residual': 10.0, 'amount_residual_currency': 90.0, 'reconciled': False}, {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, ]) - line_4 = self._create_line_for_reconciliation(-30.0, -90.0, foreign_curr, '2016-01-01') + line_4 = self.create_line_for_reconciliation(-30.0, -90.0, foreign_curr, '2016-01-01') (line_2 + line_4).reconcile() self.assertRecordValues(line_2 + line_4, [ {'amount_residual': 0.0, 'amount_residual_currency': 0.0, 'reconciled': True}, @@ -2494,9 +2467,9 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): comp_curr = self.company_data['currency'] foreign_curr1 = self.currency_data['currency'] - line_1 = self._create_line_for_reconciliation(600.0, 1200.0, foreign_curr1, '2017-01-01') - line_2 = self._create_line_for_reconciliation(-800.0, -2400.0, foreign_curr1, '2016-01-01') - line_3 = self._create_line_for_reconciliation(400.0, 400.0, comp_curr, '2016-01-01') + line_1 = self.create_line_for_reconciliation(600.0, 1200.0, foreign_curr1, '2017-01-01') + line_2 = self.create_line_for_reconciliation(-800.0, -2400.0, foreign_curr1, '2016-01-01') + line_3 = self.create_line_for_reconciliation(400.0, 400.0, comp_curr, '2016-01-01') # Create the partials as it should be created in previous version. self.env['account.partial.reconcile'].create([ @@ -2522,8 +2495,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): ]) # Fix 'line_2' & 'line_4' using the "new" reconciliation. - line_4 = self._create_line_for_reconciliation(0.0, 600.0, foreign_curr1, '2016-01-01') - line_5 = self._create_line_for_reconciliation(-200.0, -200.0, comp_curr, '2016-01-01') + line_4 = self.create_line_for_reconciliation(0.0, 600.0, foreign_curr1, '2016-01-01') + line_5 = self.create_line_for_reconciliation(-200.0, -200.0, comp_curr, '2016-01-01') (line_2 + line_3 + line_4 + line_5).reconcile() self.assertRecordValues(line_1 + line_2 + line_3 + line_4 + line_5, [ @@ -2576,11 +2549,11 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): 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') + 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. diff --git a/addons/account/tests/test_transfer_wizard.py b/addons/account/tests/test_transfer_wizard.py index 6ef3a7e9bc2..caac812a0cc 100644 --- a/addons/account/tests/test_transfer_wizard.py +++ b/addons/account/tests/test_transfer_wizard.py @@ -195,9 +195,8 @@ class TestTransferWizard(AccountTestInvoicingCommon): active_move_lines = (self.move_1 + self.move_2).mapped('line_ids').filtered(lambda x: x.account_id.account_type in ('asset_receivable', 'liability_payable')) # We use a form to pass the context properly to the depends_context move_line_ids field - context = {'active_model': 'account.move.line', 'active_ids': active_move_lines.ids} + context = {'active_model': 'account.move.line', 'active_ids': active_move_lines.ids, 'default_action': 'change_account'} with Form(self.env['account.automatic.entry.wizard'].with_context(context)) as wizard_form: - wizard_form.action = 'change_account' wizard_form.destination_account_id = self.receivable_account wizard_form.journal_id = self.journal wizard = wizard_form.save() @@ -221,9 +220,8 @@ class TestTransferWizard(AccountTestInvoicingCommon): active_move_lines = (self.move_1 + self.move_2).mapped('line_ids').filtered(lambda x: x.name in ('test1_3', 'test1_4', 'test1_5', 'test2_3', 'test2_4', 'test2_5', 'test2_6', 'test2_8')) # We use a form to pass the context properly to the depends_context move_line_ids field - context = {'active_model': 'account.move.line', 'active_ids': active_move_lines.ids} + context = {'active_model': 'account.move.line', 'active_ids': active_move_lines.ids, 'default_action': 'change_account'} with Form(self.env['account.automatic.entry.wizard'].with_context(context)) as wizard_form: - wizard_form.action = 'change_account' wizard_form.destination_account_id = self.accounts[4] wizard_form.journal_id = self.journal wizard = wizard_form.save() @@ -252,9 +250,8 @@ class TestTransferWizard(AccountTestInvoicingCommon): active_move_lines = self.move_1.mapped('line_ids').filtered(lambda x: x.name in ('test1_6', 'test1_9')) # We use a form to pass the context properly to the depends_context move_line_ids field - context = {'active_model': 'account.move.line', 'active_ids': active_move_lines.ids} + context = {'active_model': 'account.move.line', 'active_ids': active_move_lines.ids, 'default_action': 'change_account'} with Form(self.env['account.automatic.entry.wizard'].with_context(context)) as wizard_form: - wizard_form.action = 'change_account' wizard_form.destination_account_id = self.test_currency_account wizard_form.journal_id = self.journal wizard = wizard_form.save() @@ -275,9 +272,8 @@ class TestTransferWizard(AccountTestInvoicingCommon): active_move_lines = self.move_2.mapped('line_ids').filtered(lambda x: x.name in ('test2_9', 'test2_6', 'test2_8')) # We use a form to pass the context properly to the depends_context move_line_ids field - context = {'active_model': 'account.move.line', 'active_ids': active_move_lines.ids} + context = {'active_model': 'account.move.line', 'active_ids': active_move_lines.ids, 'default_action': 'change_account'} with Form(self.env['account.automatic.entry.wizard'].with_context(context)) as wizard_form: - wizard_form.action = 'change_account' wizard_form.destination_account_id = self.receivable_account wizard_form.journal_id = self.journal wizard = wizard_form.save() diff --git a/addons/account/views/account_move_views.xml b/addons/account/views/account_move_views.xml index 6e95f28aa32..db411294fbf 100644 --- a/addons/account/views/account_move_views.xml +++ b/addons/account/views/account_move_views.xml @@ -1152,7 +1152,7 @@ aria-label="Change Period" class="float-end" attrs="{'invisible': [('account_internal_group', 'not in', ('income', 'expense'))], 'column_invisible': ['|', ('parent.move_type', '=', 'entry'), ('parent.state', '!=', 'posted')]}" - context="{'hide_automatic_options': 1, 'default_action': 'change_period'}"/> + context="{'default_action': 'change_period'}"/> @@ -1368,13 +1368,22 @@ {'search_default_account_id': [active_id], 'search_default_posted': 1} - - Automatic Entries + + Move to Account code - action = records.action_automatic_entry() + action = records.action_automatic_entry('change_account') + + + + Change Period + + + + code + action = records.action_automatic_entry('change_period') diff --git a/addons/account/wizard/account_automatic_entry_wizard_views.xml b/addons/account/wizard/account_automatic_entry_wizard_views.xml index 27b73ea56c2..6911275cef0 100644 --- a/addons/account/wizard/account_automatic_entry_wizard_views.xml +++ b/addons/account/wizard/account_automatic_entry_wizard_views.xml @@ -10,11 +10,11 @@ +
The selected destination account is set to use a specific currency. Every entry transferred to it will be converted into this currency, causing the loss of any pre-existing foreign currency amount.
- @@ -47,7 +47,7 @@
- Create Automatic Entries for selected Journal Items + Transfer Journal Items account.automatic.entry.wizard form new