mirror of
https://gitlab.com/flectra-community/account-closing.git
synced 2024-11-22 21:52:04 +00:00
216 lines
8.1 KiB
Python
216 lines
8.1 KiB
Python
|
# Copyright 2016-2022 Akretion France
|
||
|
# @author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||
|
|
||
|
from flectra import _, api, fields, models
|
||
|
from flectra.exceptions import UserError, ValidationError
|
||
|
|
||
|
|
||
|
class AccountCutoff(models.Model):
|
||
|
_inherit = "account.cutoff"
|
||
|
|
||
|
@api.model
|
||
|
def _get_default_source_journals(self):
|
||
|
res = []
|
||
|
cutoff_type = self.env.context.get("default_cutoff_type")
|
||
|
mapping = {
|
||
|
"accrued_revenue": "sale",
|
||
|
"accrued_expense": "purchase",
|
||
|
"prepaid_revenue": "sale",
|
||
|
"prepaid_expense": "purchase",
|
||
|
}
|
||
|
if cutoff_type in mapping:
|
||
|
src_journals = self.env["account.journal"].search(
|
||
|
[
|
||
|
("type", "=", mapping[cutoff_type]),
|
||
|
("company_id", "=", self.env.company.id),
|
||
|
]
|
||
|
)
|
||
|
if src_journals:
|
||
|
res = src_journals.ids
|
||
|
return res
|
||
|
|
||
|
source_journal_ids = fields.Many2many(
|
||
|
"account.journal",
|
||
|
column1="cutoff_id",
|
||
|
column2="journal_id",
|
||
|
string="Source Journals",
|
||
|
default=lambda self: self._get_default_source_journals(),
|
||
|
check_company=True,
|
||
|
domain="[('company_id', '=', company_id)]",
|
||
|
)
|
||
|
state = fields.Selection(selection_add=[("forecast", "Forecast")])
|
||
|
start_date = fields.Date(help="This field is only for the forecast mode")
|
||
|
end_date = fields.Date(help="This field is only for the forecast mode")
|
||
|
|
||
|
@api.constrains("start_date", "end_date", "state")
|
||
|
def _check_start_end_dates(self):
|
||
|
for rec in self:
|
||
|
if (
|
||
|
rec.state == "forecast"
|
||
|
and rec.start_date
|
||
|
and rec.end_date
|
||
|
and rec.start_date > rec.end_date
|
||
|
):
|
||
|
raise ValidationError(_("The start date is after the end date!"))
|
||
|
|
||
|
def forecast_enable(self):
|
||
|
self.ensure_one()
|
||
|
assert self.state == "draft"
|
||
|
if self.move_id:
|
||
|
raise UserError(
|
||
|
_(
|
||
|
"This cutoff is linked to a journal entry. "
|
||
|
"You must delete it before entering forecast mode."
|
||
|
)
|
||
|
)
|
||
|
self.line_ids.unlink()
|
||
|
# set cutoff_date to False to avoid issue with unicity sql constraint
|
||
|
self.write({"state": "forecast", "cutoff_date": False})
|
||
|
|
||
|
def forecast_disable(self):
|
||
|
self.ensure_one()
|
||
|
assert self.state == "forecast"
|
||
|
self.line_ids.unlink()
|
||
|
self.write({"state": "draft"})
|
||
|
|
||
|
def _prepare_date_cutoff_line(self, aml, mapping):
|
||
|
self.ensure_one()
|
||
|
total_days = (aml.end_date - aml.start_date).days + 1
|
||
|
assert total_days > 0, "Should never happen. Total days should always be > 0"
|
||
|
# we use account mapping here
|
||
|
if aml.account_id.id in mapping:
|
||
|
cutoff_account_id = mapping[aml.account_id.id]
|
||
|
else:
|
||
|
cutoff_account_id = aml.account_id.id
|
||
|
vals = {
|
||
|
"parent_id": self.id,
|
||
|
"origin_move_line_id": aml.id,
|
||
|
"partner_id": aml.partner_id.id or False,
|
||
|
"name": aml.name,
|
||
|
"start_date": aml.start_date,
|
||
|
"end_date": aml.end_date,
|
||
|
"account_id": aml.account_id.id,
|
||
|
"cutoff_account_id": cutoff_account_id,
|
||
|
"analytic_distribution": aml.analytic_distribution,
|
||
|
"total_days": total_days,
|
||
|
"amount": -aml.balance,
|
||
|
"currency_id": self.company_currency_id.id,
|
||
|
"tax_line_ids": [],
|
||
|
}
|
||
|
if self.cutoff_type in ["prepaid_expense", "prepaid_revenue"]:
|
||
|
self._prepare_date_prepaid_cutoff_line(aml, vals)
|
||
|
elif self.cutoff_type in ["accrued_expense", "accrued_revenue"]:
|
||
|
self._prepare_date_accrual_cutoff_line(aml, vals)
|
||
|
return vals
|
||
|
|
||
|
def _prepare_date_accrual_cutoff_line(self, aml, vals):
|
||
|
self.ensure_one()
|
||
|
start_date_dt = aml.start_date
|
||
|
end_date_dt = aml.end_date
|
||
|
# Here, we compute the amount of the cutoff
|
||
|
# That's the important part !
|
||
|
cutoff_date_dt = self.cutoff_date
|
||
|
if end_date_dt <= cutoff_date_dt:
|
||
|
cutoff_days = vals["total_days"]
|
||
|
else:
|
||
|
cutoff_days = (cutoff_date_dt - start_date_dt).days + 1
|
||
|
cutoff_amount = -aml.balance * cutoff_days / vals["total_days"]
|
||
|
cutoff_amount = self.company_currency_id.round(cutoff_amount)
|
||
|
|
||
|
vals.update(
|
||
|
{
|
||
|
"cutoff_days": cutoff_days,
|
||
|
"cutoff_amount": cutoff_amount,
|
||
|
}
|
||
|
)
|
||
|
|
||
|
if aml.tax_ids and self.company_id.accrual_taxes:
|
||
|
tax_compute_all_res = aml.tax_ids.compute_all(
|
||
|
cutoff_amount,
|
||
|
product=aml.product_id,
|
||
|
partner=aml.partner_id,
|
||
|
handle_price_include=False,
|
||
|
)
|
||
|
vals["tax_line_ids"] = self._prepare_tax_lines(
|
||
|
tax_compute_all_res, self.company_currency_id
|
||
|
)
|
||
|
|
||
|
def _prepare_date_prepaid_cutoff_line(self, aml, vals):
|
||
|
self.ensure_one()
|
||
|
start_date_dt = aml.start_date
|
||
|
end_date_dt = aml.end_date
|
||
|
# Here, we compute the amount of the cutoff
|
||
|
# That's the important part !
|
||
|
if self.state == "forecast":
|
||
|
out_days = 0
|
||
|
forecast_start_date_dt = self.start_date
|
||
|
forecast_end_date_dt = self.end_date
|
||
|
if end_date_dt > forecast_end_date_dt:
|
||
|
out_days += (end_date_dt - forecast_end_date_dt).days
|
||
|
if start_date_dt < forecast_start_date_dt:
|
||
|
out_days += (forecast_start_date_dt - start_date_dt).days
|
||
|
cutoff_days = vals["total_days"] - out_days
|
||
|
else:
|
||
|
cutoff_date_dt = self.cutoff_date
|
||
|
if start_date_dt > cutoff_date_dt:
|
||
|
cutoff_days = vals["total_days"]
|
||
|
else:
|
||
|
cutoff_days = (end_date_dt - cutoff_date_dt).days
|
||
|
cutoff_amount = aml.balance * cutoff_days / vals["total_days"]
|
||
|
cutoff_amount = self.company_currency_id.round(cutoff_amount)
|
||
|
|
||
|
vals.update(
|
||
|
{
|
||
|
"cutoff_days": cutoff_days,
|
||
|
"cutoff_amount": cutoff_amount,
|
||
|
}
|
||
|
)
|
||
|
|
||
|
def get_lines(self):
|
||
|
res = super().get_lines()
|
||
|
aml_obj = self.env["account.move.line"]
|
||
|
line_obj = self.env["account.cutoff.line"]
|
||
|
if not self.source_journal_ids:
|
||
|
raise UserError(_("You should set at least one Source Journal."))
|
||
|
mapping = self._get_mapping_dict()
|
||
|
domain = [
|
||
|
("journal_id", "in", self.source_journal_ids.ids),
|
||
|
("display_type", "=", "product"),
|
||
|
("company_id", "=", self.company_id.id),
|
||
|
("balance", "!=", 0),
|
||
|
]
|
||
|
if self.source_move_state == "posted":
|
||
|
domain.append(("parent_state", "=", "posted"))
|
||
|
else:
|
||
|
domain.append(("parent_state", "in", ("draft", "posted")))
|
||
|
|
||
|
if self.cutoff_type in ["prepaid_expense", "prepaid_revenue"]:
|
||
|
if self.state == "forecast":
|
||
|
if not self.start_date or not self.end_date:
|
||
|
raise UserError(
|
||
|
_("Start date and end date are required for forecast mode.")
|
||
|
)
|
||
|
domain += [
|
||
|
("start_date", "!=", False),
|
||
|
("start_date", "<=", self.end_date),
|
||
|
("end_date", ">=", self.start_date),
|
||
|
]
|
||
|
else:
|
||
|
domain += [
|
||
|
("start_date", "!=", False),
|
||
|
("end_date", ">", self.cutoff_date),
|
||
|
("date", "<=", self.cutoff_date),
|
||
|
]
|
||
|
elif self.cutoff_type in ["accrued_expense", "accrued_revenue"]:
|
||
|
domain += [
|
||
|
("start_date", "!=", False),
|
||
|
("start_date", "<=", self.cutoff_date),
|
||
|
("date", ">", self.cutoff_date),
|
||
|
]
|
||
|
amls = aml_obj.search(domain)
|
||
|
for aml in amls:
|
||
|
line_obj.create(self._prepare_date_cutoff_line(aml, mapping))
|
||
|
|
||
|
return res
|