# Copyright 2015-2019 See manifest # License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html from odoo import _, api, fields, models from odoo.exceptions import UserError, ValidationError from odoo.tools.safe_eval import safe_eval class AccountMoveTemplate(models.Model): _name = "account.move.template" _description = "Journal Entry Template" _check_company_auto = True name = fields.Char(required=True) company_id = fields.Many2one( "res.company", string="Company", required=True, ondelete="cascade", default=lambda self: self.env.company, ) journal_id = fields.Many2one( "account.journal", string="Journal", required=True, check_company=True, domain="[('company_id', '=', company_id)]", ) ref = fields.Char(string="Reference", copy=False) line_ids = fields.One2many( "account.move.template.line", inverse_name="template_id", string="Lines" ) active = fields.Boolean(default=True) _sql_constraints = [ ( "name_company_unique", "unique(name, company_id)", "This name is already used by another template!", ) ] def copy(self, default=None): self.ensure_one() default = dict(default or {}, name=_("%s (copy)") % self.name) return super().copy(default) def eval_computed_line(self, line, sequence2amount): safe_eval_dict = {} for seq, amount in sequence2amount.items(): safe_eval_dict["L%d" % seq] = amount try: val = safe_eval(line.python_code, safe_eval_dict) sequence2amount[line.sequence] = val except ValueError as err: raise UserError( _( "Impossible to compute the formula of line with sequence %(sequence)s " "(formula: %(code)s). Check that the lines used in the formula " "really exists and have a lower sequence than the current " "line.", sequence=line.sequence, code=line.python_code, ) ) from err except SyntaxError as err: raise UserError( _( "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, ) ) from err def compute_lines(self, sequence2amount): company_cur = self.company_id.currency_id input_sequence2amount = sequence2amount.copy() for line in self.line_ids.filtered(lambda x: x.type == "input"): if line.sequence not in sequence2amount: 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." ) ) input_sequence2amount.pop(line.sequence) if input_sequence2amount: 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"): self.eval_computed_line(line, sequence2amount) sequence2amount[line.sequence] = company_cur.round( sequence2amount[line.sequence] ) return sequence2amount def generate_journal_entry(self): """Called by the button on the form view""" self.ensure_one() wiz = self.env["account.move.template.run"].create({"template_id": self.id}) action = wiz.load_lines() return action class AccountMoveTemplateLine(models.Model): _name = "account.move.template.line" _description = "Journal Item Template" _order = "sequence, id" _inherit = "analytic.mixin" _check_company_auto = True template_id = fields.Many2one( "account.move.template", string="Move Template", ondelete="cascade" ) name = fields.Char(string="Label") sequence = fields.Integer(required=True) account_id = fields.Many2one( "account.account", string="Account", required=True, domain="[('company_id', '=', company_id), ('deprecated', '=', False)]", check_company=True, ) partner_id = fields.Many2one( "res.partner", string="Partner", domain=["|", ("parent_id", "=", False), ("is_company", "=", True)], ) tax_ids = fields.Many2many("account.tax", string="Taxes", check_company=True) tax_line_id = fields.Many2one( "account.tax", string="Originator Tax", ondelete="restrict", check_company=True ) company_id = fields.Many2one(related="template_id.company_id", store=True) company_currency_id = fields.Many2one( related="template_id.company_id.currency_id", string="Company Currency", store=True, ) note = fields.Char() type = fields.Selection( [ ("input", "User input"), ("computed", "Computed"), ], required=True, default="input", ) python_code = fields.Text(string="Formula") move_line_type = fields.Selection( [("cr", "Credit"), ("dr", "Debit")], required=True, string="Direction" ) payment_term_id = fields.Many2one( "account.payment.term", string="Payment Terms", help="Used to compute the due date of the journal item.", ) is_refund = fields.Boolean( default=False, string="Is a refund?", ) tax_repartition_line_id = fields.Many2one( "account.tax.repartition.line", string="Tax Repartition Line", compute="_compute_tax_repartition_line_id", store=True, ) opt_account_id = fields.Many2one( "account.account", string="Account if Negative", domain="[('company_id', '=', company_id), ('deprecated', '=', False)]", check_company=True, help="When amount is negative, use this account instead", ) @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, ) @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 _sql_constraints = [ ( "sequence_template_uniq", "unique(template_id, sequence)", "The sequence of the line must be unique per template!", ) ] @api.constrains("type", "python_code") def check_python_code(self): for line in self: if line.type == "computed" and not line.python_code: raise ValidationError( _("Python Code must be set for computed line with sequence %d.") % line.sequence )