2019-04-11 15:49:20 +02:00
|
|
|
# Copyright 2015-2019 See manifest
|
2017-08-23 03:48:14 +02:00
|
|
|
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html
|
|
|
|
|
2020-04-24 17:27:55 +02:00
|
|
|
from odoo import _, api, fields, models
|
2019-04-11 15:49:20 +02:00
|
|
|
from odoo.exceptions import UserError, ValidationError
|
2020-04-24 17:27:55 +02:00
|
|
|
from odoo.tools.safe_eval import safe_eval
|
2017-08-23 03:48:14 +02:00
|
|
|
|
|
|
|
|
|
|
|
class AccountMoveTemplate(models.Model):
|
2020-04-24 17:27:55 +02:00
|
|
|
_name = "account.move.template"
|
|
|
|
_description = "Journal Entry Template"
|
2023-06-09 19:17:38 +02:00
|
|
|
_check_company_auto = True
|
2017-08-23 03:48:14 +02:00
|
|
|
|
2019-04-11 15:49:20 +02:00
|
|
|
name = fields.Char(required=True)
|
2017-08-23 03:48:14 +02:00
|
|
|
company_id = fields.Many2one(
|
2020-04-24 17:27:55 +02:00
|
|
|
"res.company",
|
|
|
|
string="Company",
|
|
|
|
required=True,
|
|
|
|
ondelete="cascade",
|
2020-11-20 04:59:59 +01:00
|
|
|
default=lambda self: self.env.company,
|
2020-04-24 17:27:55 +02:00
|
|
|
)
|
2023-06-09 19:17:38 +02:00
|
|
|
journal_id = fields.Many2one(
|
|
|
|
"account.journal",
|
|
|
|
string="Journal",
|
|
|
|
required=True,
|
|
|
|
check_company=True,
|
|
|
|
domain="[('company_id', '=', company_id)]",
|
|
|
|
)
|
2020-04-24 17:27:55 +02:00
|
|
|
ref = fields.Char(string="Reference", copy=False)
|
2019-04-11 15:49:20 +02:00
|
|
|
line_ids = fields.One2many(
|
2020-04-24 17:27:55 +02:00
|
|
|
"account.move.template.line", inverse_name="template_id", string="Lines"
|
|
|
|
)
|
2021-06-07 15:40:33 +02:00
|
|
|
active = fields.Boolean(default=True)
|
2019-04-11 15:49:20 +02:00
|
|
|
|
2020-04-24 17:27:55 +02:00
|
|
|
_sql_constraints = [
|
|
|
|
(
|
|
|
|
"name_company_unique",
|
|
|
|
"unique(name, company_id)",
|
|
|
|
"This name is already used by another template!",
|
|
|
|
)
|
|
|
|
]
|
2017-08-23 03:48:14 +02:00
|
|
|
|
2019-04-11 15:49:20 +02:00
|
|
|
def copy(self, default=None):
|
2017-08-23 03:48:14 +02:00
|
|
|
self.ensure_one()
|
2020-04-24 17:27:55 +02:00
|
|
|
default = dict(default or {}, name=_("%s (copy)") % self.name)
|
2020-11-26 14:57:05 +01:00
|
|
|
return super().copy(default)
|
2017-08-23 03:48:14 +02:00
|
|
|
|
2019-04-11 15:49:20 +02:00
|
|
|
def eval_computed_line(self, line, sequence2amount):
|
|
|
|
safe_eval_dict = {}
|
|
|
|
for seq, amount in sequence2amount.items():
|
2020-04-24 17:27:55 +02:00
|
|
|
safe_eval_dict["L%d" % seq] = amount
|
2019-04-11 15:49:20 +02:00
|
|
|
try:
|
|
|
|
val = safe_eval(line.python_code, safe_eval_dict)
|
|
|
|
sequence2amount[line.sequence] = val
|
2022-06-03 01:15:16 +02:00
|
|
|
except ValueError as err:
|
2020-04-24 17:27:55 +02:00
|
|
|
raise UserError(
|
|
|
|
_(
|
2022-06-03 01:15:16 +02:00
|
|
|
"Impossible to compute the formula of line with sequence %(sequence)s "
|
|
|
|
"(formula: %(code)s). Check that the lines used in the formula "
|
2020-04-24 17:27:55 +02:00
|
|
|
"really exists and have a lower sequence than the current "
|
2022-06-03 01:15:16 +02:00
|
|
|
"line.",
|
|
|
|
sequence=line.sequence,
|
|
|
|
code=line.python_code,
|
2020-04-24 17:27:55 +02:00
|
|
|
)
|
2022-06-03 01:15:16 +02:00
|
|
|
) from err
|
|
|
|
except SyntaxError as err:
|
2020-04-24 17:27:55 +02:00
|
|
|
raise UserError(
|
|
|
|
_(
|
2022-06-03 01:15:16 +02:00
|
|
|
"Impossible to compute the formula of line with sequence %(sequence)s "
|
|
|
|
"(formula: %(code)s): the syntax of the formula is wrong.",
|
|
|
|
sequence=line.sequence,
|
|
|
|
code=line.python_code,
|
2020-04-24 17:27:55 +02:00
|
|
|
)
|
2022-06-03 01:15:16 +02:00
|
|
|
) from err
|
2019-04-11 15:49:20 +02:00
|
|
|
|
|
|
|
def compute_lines(self, sequence2amount):
|
2020-11-26 14:57:05 +01:00
|
|
|
company_cur = self.company_id.currency_id
|
2019-04-11 15:49:20 +02:00
|
|
|
input_sequence2amount = sequence2amount.copy()
|
2020-04-24 17:27:55 +02:00
|
|
|
for line in self.line_ids.filtered(lambda x: x.type == "input"):
|
2019-04-11 15:49:20 +02:00
|
|
|
if line.sequence not in sequence2amount:
|
2020-04-24 17:27:55 +02:00
|
|
|
raise UserError(
|
|
|
|
_(
|
|
|
|
"You deleted a line in the wizard. This is not allowed: "
|
|
|
|
"you should either update the template or modify the "
|
|
|
|
"journal entry that will be generated by this wizard."
|
|
|
|
)
|
|
|
|
)
|
2019-04-11 15:49:20 +02:00
|
|
|
input_sequence2amount.pop(line.sequence)
|
|
|
|
if input_sequence2amount:
|
2020-04-24 17:27:55 +02:00
|
|
|
raise UserError(
|
|
|
|
_(
|
|
|
|
"You added a line in the wizard. This is not allowed: "
|
|
|
|
"you should either update the template or modify "
|
|
|
|
"the journal entry that will be generated by this wizard."
|
|
|
|
)
|
|
|
|
)
|
|
|
|
for line in self.line_ids.filtered(lambda x: x.type == "computed"):
|
2019-04-11 15:49:20 +02:00
|
|
|
self.eval_computed_line(line, sequence2amount)
|
2020-11-26 14:57:05 +01:00
|
|
|
sequence2amount[line.sequence] = company_cur.round(
|
|
|
|
sequence2amount[line.sequence]
|
2020-04-24 17:27:55 +02:00
|
|
|
)
|
2019-04-11 15:49:20 +02:00
|
|
|
return sequence2amount
|
|
|
|
|
|
|
|
def generate_journal_entry(self):
|
2020-04-24 17:27:55 +02:00
|
|
|
"""Called by the button on the form view"""
|
2019-04-11 15:49:20 +02:00
|
|
|
self.ensure_one()
|
2020-04-24 17:27:55 +02:00
|
|
|
wiz = self.env["account.move.template.run"].create({"template_id": self.id})
|
2019-04-11 15:49:20 +02:00
|
|
|
action = wiz.load_lines()
|
|
|
|
return action
|
2018-10-31 13:03:41 +01:00
|
|
|
|
2017-08-23 03:48:14 +02:00
|
|
|
|
|
|
|
class AccountMoveTemplateLine(models.Model):
|
2020-04-24 17:27:55 +02:00
|
|
|
_name = "account.move.template.line"
|
|
|
|
_description = "Journal Item Template"
|
|
|
|
_order = "sequence, id"
|
2023-06-09 19:17:38 +02:00
|
|
|
_inherit = "analytic.mixin"
|
|
|
|
_check_company_auto = True
|
2017-08-23 03:48:14 +02:00
|
|
|
|
2019-04-11 15:49:20 +02:00
|
|
|
template_id = fields.Many2one(
|
2020-04-24 17:27:55 +02:00
|
|
|
"account.move.template", string="Move Template", ondelete="cascade"
|
|
|
|
)
|
2023-06-09 19:17:38 +02:00
|
|
|
name = fields.Char(string="Label")
|
2022-06-03 01:15:16 +02:00
|
|
|
sequence = fields.Integer(required=True)
|
2017-08-23 03:48:14 +02:00
|
|
|
account_id = fields.Many2one(
|
2020-04-24 17:27:55 +02:00
|
|
|
"account.account",
|
|
|
|
string="Account",
|
|
|
|
required=True,
|
2023-06-09 19:17:38 +02:00
|
|
|
domain="[('company_id', '=', company_id), ('deprecated', '=', False)]",
|
|
|
|
check_company=True,
|
2020-04-24 17:27:55 +02:00
|
|
|
)
|
2019-04-11 15:49:20 +02:00
|
|
|
partner_id = fields.Many2one(
|
2020-04-24 17:27:55 +02:00
|
|
|
"res.partner",
|
|
|
|
string="Partner",
|
|
|
|
domain=["|", ("parent_id", "=", False), ("is_company", "=", True)],
|
|
|
|
)
|
2023-06-09 19:17:38 +02:00
|
|
|
tax_ids = fields.Many2many("account.tax", string="Taxes", check_company=True)
|
2019-04-11 15:49:20 +02:00
|
|
|
tax_line_id = fields.Many2one(
|
2023-06-09 19:17:38 +02:00
|
|
|
"account.tax", string="Originator Tax", ondelete="restrict", check_company=True
|
2020-04-24 17:27:55 +02:00
|
|
|
)
|
|
|
|
company_id = fields.Many2one(related="template_id.company_id", store=True)
|
2019-04-11 15:49:20 +02:00
|
|
|
company_currency_id = fields.Many2one(
|
2020-04-24 17:27:55 +02:00
|
|
|
related="template_id.company_id.currency_id",
|
|
|
|
string="Company Currency",
|
|
|
|
store=True,
|
|
|
|
)
|
2019-04-11 15:49:20 +02:00
|
|
|
note = fields.Char()
|
2020-04-24 17:27:55 +02:00
|
|
|
type = fields.Selection(
|
2023-06-09 19:17:38 +02:00
|
|
|
[
|
|
|
|
("input", "User input"),
|
|
|
|
("computed", "Computed"),
|
|
|
|
],
|
2020-04-24 17:27:55 +02:00
|
|
|
required=True,
|
|
|
|
default="input",
|
|
|
|
)
|
2023-06-09 19:17:38 +02:00
|
|
|
python_code = fields.Text(string="Formula")
|
2019-04-11 15:49:20 +02:00
|
|
|
move_line_type = fields.Selection(
|
2020-04-24 17:27:55 +02:00
|
|
|
[("cr", "Credit"), ("dr", "Debit")], required=True, string="Direction"
|
|
|
|
)
|
2019-04-16 11:15:58 +02:00
|
|
|
payment_term_id = fields.Many2one(
|
2020-04-24 17:27:55 +02:00
|
|
|
"account.payment.term",
|
|
|
|
string="Payment Terms",
|
|
|
|
help="Used to compute the due date of the journal item.",
|
|
|
|
)
|
2020-11-26 14:57:05 +01:00
|
|
|
is_refund = fields.Boolean(
|
|
|
|
default=False,
|
|
|
|
string="Is a refund?",
|
|
|
|
)
|
2020-04-24 17:30:12 +02:00
|
|
|
tax_repartition_line_id = fields.Many2one(
|
|
|
|
"account.tax.repartition.line",
|
|
|
|
string="Tax Repartition Line",
|
|
|
|
compute="_compute_tax_repartition_line_id",
|
|
|
|
store=True,
|
|
|
|
)
|
2020-11-20 04:59:59 +01:00
|
|
|
opt_account_id = fields.Many2one(
|
|
|
|
"account.account",
|
2023-11-03 13:25:04 +01:00
|
|
|
string="Account if Negative",
|
2023-06-09 19:17:38 +02:00
|
|
|
domain="[('company_id', '=', company_id), ('deprecated', '=', False)]",
|
|
|
|
check_company=True,
|
2020-11-26 14:57:05 +01:00
|
|
|
help="When amount is negative, use this account instead",
|
2020-11-20 04:59:59 +01:00
|
|
|
)
|
2020-04-24 17:30:12 +02:00
|
|
|
|
|
|
|
@api.depends("is_refund", "account_id", "tax_line_id")
|
|
|
|
def _compute_tax_repartition_line_id(self):
|
|
|
|
for record in self.filtered(lambda x: x.account_id and x.tax_line_id):
|
|
|
|
tax_repartition = "refund_tax_id" if record.is_refund else "invoice_tax_id"
|
|
|
|
record.tax_repartition_line_id = self.env[
|
|
|
|
"account.tax.repartition.line"
|
|
|
|
].search(
|
|
|
|
[
|
|
|
|
("account_id", "=", record.account_id.id),
|
|
|
|
(tax_repartition, "=", record.tax_line_id.id),
|
|
|
|
],
|
|
|
|
limit=1,
|
|
|
|
)
|
2019-04-11 15:49:20 +02:00
|
|
|
|
2023-06-09 19:17:38 +02:00
|
|
|
@api.depends("account_id", "partner_id")
|
|
|
|
def _compute_analytic_distribution(self):
|
|
|
|
for line in self:
|
|
|
|
distribution = self.env[
|
|
|
|
"account.analytic.distribution.model"
|
|
|
|
]._get_distribution(
|
|
|
|
{
|
|
|
|
"partner_id": line.partner_id.id,
|
|
|
|
"partner_category_id": line.partner_id.category_id.ids,
|
|
|
|
"product_id": False,
|
|
|
|
"product_categ_id": False,
|
|
|
|
"account_prefix": line.account_id.code,
|
|
|
|
"company_id": line.template_id.company_id.id,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
line.analytic_distribution = distribution or line.analytic_distribution
|
|
|
|
|
2020-04-24 17:27:55 +02:00
|
|
|
_sql_constraints = [
|
|
|
|
(
|
|
|
|
"sequence_template_uniq",
|
|
|
|
"unique(template_id, sequence)",
|
|
|
|
"The sequence of the line must be unique per template!",
|
|
|
|
)
|
|
|
|
]
|
2017-08-23 03:48:14 +02:00
|
|
|
|
2020-04-24 17:27:55 +02:00
|
|
|
@api.constrains("type", "python_code")
|
2019-04-11 15:49:20 +02:00
|
|
|
def check_python_code(self):
|
|
|
|
for line in self:
|
2020-04-24 17:27:55 +02:00
|
|
|
if line.type == "computed" and not line.python_code:
|
|
|
|
raise ValidationError(
|
2023-06-09 19:17:38 +02:00
|
|
|
_("Python Code must be set for computed line with sequence %d.")
|
2020-04-24 17:27:55 +02:00
|
|
|
% line.sequence
|
|
|
|
)
|