account-closing/account_multicurrency_revaluation/wizard/wizard_currency_revaluation.py
2023-05-19 10:19:13 +02:00

359 lines
13 KiB
Python

# Copyright 2012-2018 Camptocamp SA
# Copyright 2020 CorporateHub (https://corporatehub.eu)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from flectra import _, api, exceptions, fields, models
from flectra.tools import float_repr
class WizardCurrencyRevaluation(models.TransientModel):
_name = "wizard.currency.revaluation"
_description = "Currency Revaluation Wizard"
@api.model
def _get_default_revaluation_date(self):
return fields.Date.today()
@api.model
def _get_default_journal_id(self):
return self.env.user.company_id.currency_reval_journal_id
@api.model
def _get_default_label(self):
return "%(currency)s %(account)s %(rate)s currency revaluation"
revaluation_date = fields.Date(
string="Revaluation Date",
required=True,
default=lambda self: self._get_default_revaluation_date(),
)
journal_id = fields.Many2one(
comodel_name="account.journal",
string="Journal",
domain=[("type", "=", "general")],
help="You can set the default journal in company settings.",
required=True,
default=lambda self: self._get_default_journal_id(),
)
label = fields.Char(
string="Entry description",
size=100,
help="This label will be inserted in entries description. "
"You can use %(account)s, %(account_name)s, %(currency)s and "
"%(rate)s keywords.",
required=True,
default=lambda self: self._get_default_label(),
)
def _create_move_and_lines(
self,
amount,
debit_account_id,
credit_account_id,
sums,
label,
form,
partner_id,
currency_id,
analytic_debit_acc_id=False,
analytic_credit_acc_id=False,
):
base_move = {
"journal_id": form.journal_id.id,
"date": form.revaluation_date,
}
if form.journal_id.company_id.reversable_revaluations:
base_move["revaluation_to_reverse"] = True
base_line = {
"name": label,
"partner_id": partner_id,
"currency_id": currency_id,
"amount_currency": 0.0,
"date": form.revaluation_date,
}
base_line["gl_foreign_balance"] = sums.get("foreign_balance", 0.0)
base_line["gl_balance"] = sums.get("balance", 0.0)
base_line["gl_revaluated_balance"] = sums.get("revaluated_balance", 0.0)
base_line["gl_currency_rate"] = sums.get("currency_rate", 0.0)
debit_line = base_line.copy()
credit_line = base_line.copy()
debit_line.update(
{"debit": amount, "credit": 0.0, "account_id": debit_account_id}
)
if analytic_debit_acc_id:
credit_line.update({"analytic_account_id": analytic_debit_acc_id})
credit_line.update(
{"debit": 0.0, "credit": amount, "account_id": credit_account_id}
)
if analytic_credit_acc_id:
credit_line.update({"analytic_account_id": analytic_credit_acc_id})
base_move["line_ids"] = [(0, 0, debit_line), (0, 0, credit_line)]
created_move = self.env["account.move"].create(base_move)
if self.journal_id.company_id.auto_post_entries:
created_move.action_post()
return [x.id for x in created_move.line_ids]
def _compute_unrealized_currency_gl(self, currency, balances):
"""
Update data dict with the unrealized currency gain and loss
plus add 'currency_rate' which is the value used for rate in
computation
@param int currency: currency to revaluate
@param dict balances: contains foreign balance and balance
@return: updated data for foreign balance plus rate value used
"""
balance = balances.get("balance", 0.0)
if currency == self.journal_id.company_id.currency_id:
return {
"currency_rate": 1.0,
"unrealized_gain_loss": 0.0,
"revaluated_balance": balance,
}
foreign_balance = balances.get("foreign_balance", 0.0)
adjusted_balance = currency._convert(
foreign_balance,
self.journal_id.company_id.currency_id,
self.journal_id.company_id,
self.revaluation_date,
)
unrealized_gain_loss = adjusted_balance - balance
return {
"currency_rate": currency.rate,
"unrealized_gain_loss": unrealized_gain_loss,
"revaluated_balance": adjusted_balance,
}
@api.model
def _format_balance_adjustment_label(self, fmt, account, currency, rate):
return fmt % {
"account": account.code or _("N/A"),
"account_name": account.name or _("N/A"),
"currency": currency.name or _("N/A"),
"rate": float_repr(rate, 6),
}
def _write_adjust_balance(
self, account, currency, partner_id, amount, label, form, sums
):
if partner_id is None:
partner_id = False
company = form.journal_id.company_id or self.env.user.company_id
created_ids = []
amount_vs_zero = currency.compare_amounts(amount, 0.0)
if amount_vs_zero == 1:
if company.revaluation_gain_account_id:
line_ids = self._create_move_and_lines(
amount,
account.id,
company.revaluation_gain_account_id.id,
sums,
label,
form,
partner_id,
currency.id,
analytic_credit_acc_id=(company.revaluation_analytic_account_id.id),
)
created_ids.extend(line_ids)
if (
company.provision_bs_gain_account_id
and company.provision_pl_gain_account_id
):
line_ids = self._create_move_and_lines(
amount,
company.provision_bs_gain_account_id.id,
company.provision_pl_gain_account_id.id,
sums,
label,
form,
partner_id,
currency.id,
analytic_credit_acc_id=(
company.provision_pl_analytic_account_id.id
),
)
created_ids.extend(line_ids)
elif amount_vs_zero == -1:
if company.revaluation_loss_account_id:
line_ids = self._create_move_and_lines(
-amount,
company.revaluation_loss_account_id.id,
account.id,
sums,
label,
form,
partner_id,
currency.id,
analytic_debit_acc_id=(company.revaluation_analytic_account_id.id),
)
created_ids.extend(line_ids)
if (
company.provision_bs_loss_account_id
and company.provision_pl_loss_account_id
):
line_ids = self._create_move_and_lines(
-amount,
company.provision_pl_loss_account_id.id,
company.provision_bs_loss_account_id.id,
sums,
label,
form,
partner_id,
currency.id,
analytic_debit_acc_id=(company.provision_pl_analytic_account_id.id),
)
created_ids.extend(line_ids)
return created_ids
def _validate_company_revaluation_configuration(self, company):
return (
(
company.revaluation_loss_account_id
and company.revaluation_gain_account_id
)
or (
company.provision_bs_loss_account_id
and company.provision_pl_loss_account_id
)
or (
company.provision_bs_gain_account_id
and company.provision_pl_gain_account_id
)
)
def revaluate_currency(self):
"""
Compute unrealized currency gain and loss and add entries to
adjust balances
@return: dict to open an Entries view filtered on generated move lines
"""
Account = self.env["account.account"]
Currency = self.env["res.currency"]
company = self.journal_id.company_id or self.env.user.company_id
if not self._validate_company_revaluation_configuration(company):
raise exceptions.UserError(
_(
"No revaluation or provision account are defined"
" for your company.\n"
"You must specify at least one provision account or"
" a couple of provision account in the accounting settings."
)
)
# Search for accounts Balance Sheet to be revaluated
# on those criteria
# - deferral method of account type is not None
account_ids = Account.search(
[
("user_type_id.include_initial_balance", "=", "True"),
("currency_revaluation", "=", True),
("company_id", "=", company.id),
]
)
if not account_ids:
raise exceptions.UserError(
_(
"No account to be revaluated found. "
"Please check 'Allow Currency Revaluation' "
"for at least one account in account form."
)
)
revaluations = account_ids.compute_revaluations(self.revaluation_date)
for account_id, by_account in revaluations.items():
account = Account.browse(account_id)
if account.internal_type == "liquidity" and (
not account.currency_id
or account.currency_id == account.company_id.currency_id
):
# NOTE: There's no point of revaluating anying on bank account
# if bank account currency matches company currency.
continue
for partner_id, by_partner in by_account.items():
for currency_id, lines in by_partner.items():
currency = Currency.browse(currency_id).with_context(
date=self.revaluation_date
)
diff_balances = self._compute_unrealized_currency_gl(
currency, lines
)
revaluations[account_id][partner_id][currency_id].update(
diff_balances
)
# Create entries only after all computation have been done
created_ids = []
for account_id, by_account in revaluations.items():
account = Account.browse(account_id)
for partner_id, by_partner in by_account.items():
for currency_id, lines in by_partner.items():
currency = Currency.browse(currency_id)
adj_balance = lines.get("unrealized_gain_loss", 0.0)
if currency.is_zero(adj_balance):
continue
rate = lines.get("currency_rate", 0.0)
label = self._format_balance_adjustment_label(
self.label, account, currency, rate
)
new_ids = self._write_adjust_balance(
account, currency, partner_id, adj_balance, label, self, lines
)
created_ids.extend(new_ids)
# In case revaluation date is before today, it's safe to run reversing
# w/o waiting tomorrow, since otherwise it would cause confusion when
# revaluating historical entries for multiple years within one day.
if (
self.journal_id.company_id.reversable_revaluations
and self.revaluation_date < fields.Date.context_today(self)
):
reversing_moves = self.env["account.move"].search(
[
("journal_id", "in", self.mapped("journal_id.id")),
("state", "=", "posted"),
("revaluation_to_reverse", "=", True),
("reversed_entry_id", "=", False),
],
limit=1,
)
reversing_moves._reverse_moves()
if created_ids:
return {
"domain": [("id", "in", created_ids)],
"name": _("Created revaluation lines"),
"view_mode": "tree,form",
"auto_search": True,
"res_model": "account.move.line",
"view_id": False,
"search_view_id": False,
"type": "ir.actions.act_window",
}
else:
raise exceptions.UserError(_("No accounting entry has been posted."))