# 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" 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) 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" template_id = fields.Many2one( "account.move.template", string="Move Template", ondelete="cascade" ) name = fields.Char(string="Label", required=True) sequence = fields.Integer(required=True) account_id = fields.Many2one( "account.account", string="Account", required=True, domain=[("deprecated", "=", False)], ) partner_id = fields.Many2one( "res.partner", string="Partner", domain=["|", ("parent_id", "=", False), ("is_company", "=", True)], ) analytic_account_id = fields.Many2one( "account.analytic.account", string="Analytic Account" ) analytic_tag_ids = fields.Many2many("account.analytic.tag", string="Analytic Tags") tax_ids = fields.Many2many("account.tax", string="Taxes") tax_line_id = fields.Many2one( "account.tax", string="Originator Tax", ondelete="restrict" ) 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( [("computed", "Computed"), ("input", "User input")], required=True, default="input", ) python_code = fields.Text() 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, readonly=True, ) opt_account_id = fields.Many2one( "account.account", string="Account Opt.", domain=[("deprecated", "=", False)], 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, ) _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 )