oca-technical/odoo-bringout-oca-account-closing-account_multicurrency_revaluation/account_multicurrency_revaluation/model/account.py
2025-08-29 15:43:03 +02:00

318 lines
12 KiB
Python

# Copyright 2012-2018 Camptocamp SA
# Copyright 2020 CorporateHub (https://corporatehub.eu)
# Copyright 2022 ForgeFlow S.L. (https://www.forgeflow.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class AccountAccountLine(models.Model):
_inherit = "account.move.line"
# By convention added columns start with gl_.
gl_foreign_balance = fields.Float(string="Aggregated Amount currency")
gl_balance = fields.Float(string="Aggregated Amount")
gl_revaluated_balance = fields.Float(string="Revaluated Amount")
gl_currency_rate = fields.Float(string="Currency rate")
revaluation_created_line_id = fields.Many2one(
comodel_name="account.move.line",
string="Revaluation Created Line",
readonly=True,
)
revaluation_origin_line_ids = fields.One2many(
comodel_name="account.move.line",
inverse_name="revaluation_created_line_id",
string="Revaluation Origin Lines",
readonly=True,
)
revaluation_origin_line_count = fields.Integer(
compute="_compute_revaluation_origin_line_count"
)
def _compute_revaluation_origin_line_count(self):
for line in self:
line.revaluation_origin_line_count = len(line.revaluation_origin_line_ids)
def action_view_revaluation_origin_lines(self):
self.ensure_one()
action = self.env["ir.actions.act_window"]._for_xml_id(
"account.action_account_moves_all"
)
action["context"] = {}
if len(self.revaluation_origin_line_ids) > 1:
action["domain"] = [("id", "in", self.revaluation_origin_line_ids.ids)]
elif self.revaluation_origin_line_ids:
form_view = [(self.env.ref("account.view_move_line_form").id, "form")]
if "views" in action:
action["views"] = form_view + [
(state, view) for state, view in action["views"] if view != "form"
]
else:
action["views"] = form_view
action["res_id"] = self.revaluation_origin_line_ids.id
else:
action = {"type": "ir.actions.act_window_close"}
return action
def action_view_revaluation_created_line(self):
self.ensure_one()
action = self.env["ir.actions.act_window"]._for_xml_id(
"account.action_account_moves_all"
)
action["context"] = {}
if self.revaluation_created_line_id:
form_view = [(self.env.ref("account.view_move_line_form").id, "form")]
if "views" in action:
action["views"] = form_view + [
(state, view) for state, view in action["views"] if view != "form"
]
else:
action["views"] = form_view
action["res_id"] = self.revaluation_created_line_id.id
else:
action = {"type": "ir.actions.act_window_close"}
return action
class AccountAccount(models.Model):
_inherit = "account.account"
currency_revaluation = fields.Boolean(
string="Allow Currency Revaluation",
compute="_compute_currency_revaluation",
store=True,
readonly=False,
)
_sql_mapping = {
"balance": "COALESCE(SUM(debit),0) - COALESCE(SUM(credit), 0) as balance",
"debit": "COALESCE(SUM(debit), 0) as debit",
"credit": "COALESCE(SUM(credit), 0) as credit",
"foreign_balance": "COALESCE(SUM(amount_currency), 0) as foreign_balance",
}
def init(self):
# all receivable, payable, Bank and Cash accounts should
# have currency_revaluation True by default
res = super().init()
accounts = self.env["account.account"].search(
[
("account_type", "in", self._get_revaluation_account_types()),
("currency_revaluation", "=", False),
("include_initial_balance", "=", True),
]
)
accounts.write({"currency_revaluation": True})
return res
def write(self, vals):
if (
"currency_revaluation" in vals
and vals.get("currency_revaluation", False)
and any([not x for x in self.mapped("include_initial_balance")])
):
raise UserError(
_(
"There is an account that you are editing not having the Bring "
"Balance Forward set, the currency revaluation cannot be applied "
"on these accounts: \n\t - %s"
)
% "\n\t - ".join(
self.filtered(lambda x: not x.include_initial_balance).mapped(
"name"
)
)
)
return super(AccountAccount, self).write(vals)
def _get_revaluation_account_types(self):
return [
"asset_receivable",
"liability_payable",
"asset_cash",
"liability_credit_card",
]
@api.depends("account_type")
def _compute_currency_revaluation(self):
for rec in self:
revaluation_accounts = rec._get_revaluation_account_types()
if rec.account_type in revaluation_accounts:
rec.currency_revaluation = True
else:
rec.currency_revaluation = False
def _revaluation_query(self, revaluation_date, start_date=None):
query = self.env["account.move.line"]._where_calc(
[
("company_id", "in", self.env.companies.ids),
("display_type", "not in", ("line_section", "line_note")),
("parent_state", "!=", "cancel"),
]
)
self.env["account.move.line"]._apply_ir_rules(query)
tables, where_clause, where_clause_params = query.get_sql()
mapping = [
('"account_move_line".', "aml."),
('"account_move_line"', "account_move_line aml"),
("LEFT JOIN", "\n LEFT JOIN"),
(")) AND", "))\n" + " " * 12 + "AND"),
]
for s_from, s_to in mapping:
tables = tables.replace(s_from, s_to)
where_clause = where_clause.replace(s_from, s_to)
where_clause = ("\n" + " " * 8 + "AND " + where_clause) if where_clause else ""
query = (
"""
WITH amount AS (
SELECT
aml.account_id,
CASE WHEN acc.account_type IN ('liability_payable', 'asset_receivable')
THEN aml.partner_id
ELSE NULL
END AS partner_id,
aml.currency_id,
aml.debit,
aml.credit,
aml.amount_currency,
aml.id as origin_aml_id
FROM """
+ tables
+ """
LEFT JOIN account_move am ON aml.move_id = am.id
INNER JOIN account_account acc ON aml.account_id = acc.id
LEFT JOIN account_partial_reconcile aprc
ON (aml.balance < 0 AND aml.id = aprc.credit_move_id)
LEFT JOIN account_move_line amlcf
ON (
aml.balance < 0
AND aprc.debit_move_id = amlcf.id
AND amlcf.date < %s
)
LEFT JOIN account_partial_reconcile aprd
ON (aml.balance > 0 AND aml.id = aprd.debit_move_id)
LEFT JOIN account_move_line amldf
ON (
aml.balance > 0
AND aprd.credit_move_id = amldf.id
AND amldf.date < %s
)
WHERE
aml.account_id IN %s
AND aml.date <= %s
"""
+ (("AND aml.date >= %s") if start_date else "")
+ """
AND aml.currency_id IS NOT NULL
AND am.state = 'posted'
AND aml.balance <> 0
"""
+ where_clause
+ """
GROUP BY
acc.account_type,
origin_aml_id
HAVING
aml.amount_residual_currency <> 0
)
SELECT
account_id as id,
origin_aml_id,
partner_id,
currency_id,"""
+ ", ".join(self._sql_mapping.values())
+ """
FROM amount
GROUP BY
account_id,
origin_aml_id,
currency_id,
partner_id
ORDER BY account_id, partner_id, currency_id"""
)
params = [
revaluation_date,
revaluation_date,
tuple(self.ids),
revaluation_date,
*where_clause_params,
]
if start_date:
# Insert the value after the revaluation date parameter
params.insert(4, start_date)
return query, params
def compute_revaluations(self, revaluation_date, start_date=None):
query, params = self._revaluation_query(revaluation_date, start_date)
self.env.cr.execute(query, params)
lines = self.env.cr.dictfetchall()
data = {}
for line in lines:
account_id, currency_id, partner_id, origin_aml_id = (
line["id"],
line["currency_id"],
line["partner_id"],
line["origin_aml_id"],
)
data.setdefault(account_id, {})
data[account_id].setdefault(partner_id, {})
data[account_id][partner_id].setdefault(currency_id, {})
# If partially reconciled, we need to adjust the balance according to
# the partially reconciled items on the current line.
origin_aml = self.env["account.move.line"].browse(origin_aml_id)
if origin_aml.matched_debit_ids | origin_aml.matched_credit_ids:
debit_move_ids = origin_aml.matched_debit_ids.mapped("debit_move_id")
credit_move_ids = origin_aml.matched_credit_ids.mapped("credit_move_id")
total_debit = line["debit"] + sum(debit_move_ids.mapped("debit"))
total_credit = line["credit"] + sum(credit_move_ids.mapped("credit"))
total_balance = total_debit - total_credit
total_balance_currency = (
line["foreign_balance"]
+ sum(debit_move_ids.mapped("amount_currency"))
+ sum(credit_move_ids.mapped("amount_currency"))
)
line.update(
{
"debit": round(total_debit, 2),
"credit": round(total_credit, 2),
"balance": round(total_balance, 2),
"foreign_balance": round(total_balance_currency, 2),
}
)
existing_line = data[account_id][partner_id][currency_id]
if existing_line:
data[account_id][partner_id][
currency_id
] = self._merge_currency_revaluation_lines(existing_line, line)
else:
# Convert origin account move lines to list as there can be multiple
line["origin_aml_id"] = [line["origin_aml_id"]]
data[account_id][partner_id][currency_id] = line
return data
@api.model
def _merge_currency_revaluation_lines(self, first_line, second_line):
resulting_line = first_line
resulting_line["origin_aml_id"].append(second_line["origin_aml_id"])
resulting_line["balance"] += second_line["balance"]
resulting_line["debit"] += second_line["debit"]
resulting_line["credit"] += second_line["credit"]
resulting_line["foreign_balance"] += second_line["foreign_balance"]
return resulting_line
class AccountMove(models.Model):
_inherit = "account.move"
revaluation_to_reverse = fields.Boolean(
string="Revaluation to reverse", default=False, readonly=True
)
revaluation_reversed = fields.Boolean(
string="Revaluation reversed", default=False, readonly=True
)