diff --git a/account_move_template/models/account_move_template.py b/account_move_template/models/account_move_template.py index 75b34f5ec..78aa888af 100644 --- a/account_move_template/models/account_move_template.py +++ b/account_move_template/models/account_move_template.py @@ -17,7 +17,7 @@ class AccountMoveTemplate(models.Model): string="Company", required=True, ondelete="cascade", - default=lambda self: self.env["res.company"]._company_default_get(), + default=lambda self: self.env.company, ) journal_id = fields.Many2one("account.journal", string="Journal", required=True) ref = fields.Char(string="Reference", copy=False) @@ -159,6 +159,12 @@ class AccountMoveTemplateLine(models.Model): 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 in stead", + ) @api.depends("is_refund", "account_id", "tax_line_id") def _compute_tax_repartition_line_id(self): diff --git a/account_move_template/readme/CONTRIBUTORS.rst b/account_move_template/readme/CONTRIBUTORS.rst index 07f3cea32..4b3b5f765 100644 --- a/account_move_template/readme/CONTRIBUTORS.rst +++ b/account_move_template/readme/CONTRIBUTORS.rst @@ -19,3 +19,7 @@ Module Contributors * Harald Panten * Valentin Vinagre + +* `Ecosoft `_: + + * Kitti U. (Add context overwrite) diff --git a/account_move_template/readme/DESCRIPTION.rst b/account_move_template/readme/DESCRIPTION.rst index 34a9f8fb0..d43635a01 100644 --- a/account_move_template/readme/DESCRIPTION.rst +++ b/account_move_template/readme/DESCRIPTION.rst @@ -7,3 +7,23 @@ the amount of every input lines. The journal entry form allows lo load, through a wizard, the template to use and the amounts to fill. + +**Notable features:** + +This module enhance the capability of module account_move_template with following features, + +#. Optional account for negative amount. + + When the Journal entry is created, and credit/debit is negative value, change debit/credit + side and use the opt_account_id + +#. Allow overwrite move line values with overwrite dict. + + Normally, the journal items created by the template will require user input on wizard. + This feature allow passing the overwrite values with a dictionary. + This is particularly useful when the wizard is called by code. + + Sample of dictionary to overwrite move lines:: + + {'L1': {'partner_id': 1, 'amount': 100, 'name': 'some label'}, + 'L2': {'partner_id': 2, 'amount': 200, 'name': 'some label 2'}, } diff --git a/account_move_template/readme/USAGE.rst b/account_move_template/readme/USAGE.rst index 57f46c969..969133132 100644 --- a/account_move_template/readme/USAGE.rst +++ b/account_move_template/readme/USAGE.rst @@ -10,4 +10,5 @@ To use an existing template: #. Go to *Invoicing / Accounting / Miscellaneous / Create Entry from Template* #. Select one of the available templates. +#. As option, you can overwrite output lines with dict, i.e., {"L1": {"partner": 1}} #. Complete the entries according to the template and click on the button *Generate Journal Entry*. diff --git a/account_move_template/tests/__init__.py b/account_move_template/tests/__init__.py index 40847a7a0..6786498ff 100644 --- a/account_move_template/tests/__init__.py +++ b/account_move_template/tests/__init__.py @@ -1,3 +1,4 @@ # Needs to be re-written because wizard.multi.charts.accounts # doesn't exist any more on v12 # from . import test_account_move_template +from . import test_account_move_template_options diff --git a/account_move_template/tests/test_account_move_template_options.py b/account_move_template/tests/test_account_move_template_options.py new file mode 100644 index 000000000..e8fc84b7e --- /dev/null +++ b/account_move_template/tests/test_account_move_template_options.py @@ -0,0 +1,186 @@ +# Copyright 2020 Ecosoft (http://ecosoft.co.th) +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html +from odoo.exceptions import ValidationError +from odoo.tests.common import Form, TransactionCase + + +class TestAccountMoveTemplateEnhanced(TransactionCase): + def setUp(self): + super(TestAccountMoveTemplateEnhanced, self).setUp() + self.Move = self.env["account.move"] + self.Journal = self.env["account.journal"] + self.Account = self.env["account.account"] + self.Template = self.env["account.move.template"] + self.Partner = self.env["res.partner"] + + self.journal = self.Journal.search([("type", "=", "general")], limit=1) + self.ar_account_id = self.Account.search( + [("user_type_id.type", "=", "receivable")], limit=1 + ) + self.ap_account_id = self.Account.search( + [("user_type_id.type", "=", "payable")], limit=1 + ) + self.income_account_id = self.Account.search( + [ + ("user_type_id.type", "=", "other"), + ("user_type_id.internal_group", "=", "income"), + ], + limit=1, + ) + self.expense_account_id = self.Account.search( + [ + ("user_type_id.type", "=", "other"), + ("user_type_id.internal_group", "=", "expense"), + ], + limit=1, + ) + self.partners = self.Partner.search([], limit=3) + + # Create a simple move tempalte + ar_line = { + "sequence": 0, + "name": "AR Line 1", + "account_id": self.ar_account_id.id, + "opt_account_id": self.ap_account_id.id, + "move_line_type": "dr", + "type": "input", + } + income_line1 = { + "sequence": 1, + "name": "Income Line 2", + "account_id": self.income_account_id.id, + "opt_account_id": self.expense_account_id.id, + "move_line_type": "cr", + "type": "computed", + "python_code": "L0*1/3", + } + income_line2 = { + "sequence": 2, + "name": "Income Line 2", + "account_id": self.income_account_id.id, + "opt_account_id": self.expense_account_id.id, + "move_line_type": "cr", + "type": "computed", + "python_code": "L0*2/3", + } + + self.move_template = self.Template.create( + { + "name": "Test Template", + "journal_id": self.journal.id, + "line_ids": [ + (0, 0, ar_line), + (0, 0, income_line1), + (0, 0, income_line2), + ], + } + ) + + def test_move_template_normal(self): + """ Test normal case, input amount 300 """ + with Form(self.env["account.move.template.run"]) as f: + f.template_id = self.move_template + template_run = f.save() + template_run.load_lines() + template_run.line_ids[0].amount = 300 + res = template_run.generate_move() + move = self.Move.browse(res["res_id"]) + self.assertRecordValues( + move.line_ids.sorted("credit"), + [ + {"account_id": self.ar_account_id.id, "credit": 0.0, "debit": 300.0}, + { + "account_id": self.income_account_id.id, + "credit": 100.0, + "debit": 0.0, + }, + { + "account_id": self.income_account_id.id, + "credit": 200.0, + "debit": 0.0, + }, + ], + ) + + def test_move_template_optional(self): + """ Test optional case, input amount -300, expect optional account """ + with Form(self.env["account.move.template.run"]) as f: + f.template_id = self.move_template + template_run = f.save() + template_run.load_lines() + template_run.line_ids[0].amount = -300 # Negative amount + res = template_run.generate_move() + move = self.Move.browse(res["res_id"]) + self.assertRecordValues( + move.line_ids.sorted("debit"), + [ + {"account_id": self.ap_account_id.id, "credit": 300.0, "debit": 0.0}, + { + "account_id": self.expense_account_id.id, + "credit": 0.0, + "debit": 100.0, + }, + { + "account_id": self.expense_account_id.id, + "credit": 0.0, + "debit": 200.0, + }, + ], + ) + + def test_move_template_overwrite(self): + """ Test case overwrite, amount = 3000, no need to manual input """ + # Test for error when debit is not a valid field + with Form(self.env["account.move.template.run"]) as f: + f.template_id = self.move_template + f.overwrite = str( + { + "L0": { + "partner_id": self.partners[0].id, + "amount": 3000, + "debit": 3000, + }, + } + ) + template_run = f.save() + with self.assertRaises(ValidationError): + template_run.load_lines() + # Assign only on valid fields, and load_lines again + with Form(self.env["account.move.template.run"]) as f: + f.template_id = self.move_template + f.overwrite = str( + { + "L0": {"partner_id": self.partners[0].id, "amount": 3000}, + "L1": {"partner_id": self.partners[1].id}, + "L2": {"partner_id": self.partners[2].id}, + } + ) + template_run = f.save() + res = template_run.load_lines() + self.assertEqual(template_run.line_ids[0].partner_id, self.partners[0]) + self.assertEqual(template_run.line_ids[0].amount, 3000) + res = template_run.with_context(res["context"]).generate_move() + move = self.Move.browse(res["res_id"]) + self.assertRecordValues( + move.line_ids.sorted("credit"), + [ + { + "partner_id": self.partners[0].id, + "account_id": self.ar_account_id.id, + "credit": 0.0, + "debit": 3000.0, + }, + { + "partner_id": self.partners[1].id, + "account_id": self.income_account_id.id, + "credit": 1000.0, + "debit": 0.0, + }, + { + "partner_id": self.partners[2].id, + "account_id": self.income_account_id.id, + "credit": 2000.0, + "debit": 0.0, + }, + ], + ) diff --git a/account_move_template/view/account_move_template.xml b/account_move_template/view/account_move_template.xml index 02b555a29..730fd5f01 100644 --- a/account_move_template/view/account_move_template.xml +++ b/account_move_template/view/account_move_template.xml @@ -7,6 +7,10 @@ + + diff --git a/account_move_template/wizard/account_move_template_run.py b/account_move_template/wizard/account_move_template_run.py index b9b6068e1..f5cd26f81 100644 --- a/account_move_template/wizard/account_move_template_run.py +++ b/account_move_template/wizard/account_move_template_run.py @@ -1,8 +1,9 @@ # Copyright 2015-2019 See manifest # License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html +from ast import literal_eval from odoo import _, fields, models -from odoo.exceptions import UserError +from odoo.exceptions import UserError, ValidationError from odoo.tools import float_is_zero @@ -15,7 +16,7 @@ class AccountMoveTemplateRun(models.TransientModel): "res.company", required=True, readonly=True, - default=lambda self: self.env["res.company"]._company_default_get(), + default=lambda self: self.env.company, ) partner_id = fields.Many2one( "res.partner", @@ -33,6 +34,13 @@ class AccountMoveTemplateRun(models.TransientModel): readonly=True, default="select_template", ) + overwrite = fields.Text( + help=""" +Valid dictionary to overwrite template lines: +{'L1': {'partner_id': 1, 'amount': 100, 'name': 'some label'}, + 'L2': {'partner_id': 2, 'amount': 200, 'name': 'some label 2'}, } + """ + ) def _prepare_wizard_line(self, tmpl_line): vals = { @@ -57,6 +65,8 @@ class AccountMoveTemplateRun(models.TransientModel): # STEP 1 def load_lines(self): self.ensure_one() + # Verify and get overwrite dict + overwrite_vals = self._get_overwrite_vals() amtlro = self.env["account.move.template.line.run"] if self.company_id != self.template_id.company_id: raise UserError( @@ -86,8 +96,75 @@ class AccountMoveTemplateRun(models.TransientModel): action = self.env.ref("account_move_template.account_move_template_run_action") result = action.read()[0] result.update({"res_id": self.id, "context": self.env.context}) + + # Overwrite self.line_ids to show overwrite values + self._overwrite_line(overwrite_vals) + # Pass context furtner to generate_move function, only readonly field + for key in overwrite_vals.keys(): + overwrite_vals[key].pop("amount", None) + context = result.get("context", {}).copy() + context.update({"overwrite": overwrite_vals}) + result["context"] = context return result + def _get_valid_keys(self): + return ["partner_id", "amount", "name", "date_maturity"] + + def _get_overwrite_vals(self): + """ valid_dict = { + 'L1': {'partner_id': 1, 'amount': 10}, + 'L2': {'partner_id': 2, 'amount': 20}, + } + """ + self.ensure_one() + valid_keys = self._get_valid_keys() + overwrite_vals = self.overwrite or "{}" + try: + overwrite_vals = literal_eval(overwrite_vals) + assert isinstance(overwrite_vals, dict) + except (SyntaxError, ValueError, AssertionError): + raise ValidationError(_("Overwrite value must be a valid python dict")) + # First level keys must be L1, L2, ... + keys = overwrite_vals.keys() + if list(filter(lambda x: x[:1] != "L" or not x[1:].isdigit(), keys)): + raise ValidationError(_("Keys must be line sequence, i..e, L1, L2, ...")) + # Second level keys must be a valid keys + try: + if dict( + filter(lambda x: set(overwrite_vals[x].keys()) - set(valid_keys), keys) + ): + raise ValidationError( + _("Valid fields to overwrite are %s") % valid_keys + ) + except ValidationError as e: + raise e + except Exception as e: + msg = """ + valid_dict = { + 'L1': {'partner_id': 1, 'amount': 10}, + 'L2': {'partner_id': 2, 'amount': 20}, + } + """ + raise ValidationError(_("Invalid dictionary: {}\n{}".format(e, msg))) + return overwrite_vals + + def _safe_vals(self, model, vals): + obj = self.env[model] + copy_vals = vals.copy() + invalid_keys = list( + set(list(vals.keys())) - set(list(dict(obj._fields).keys())) + ) + for key in invalid_keys: + copy_vals.pop(key) + return copy_vals + + def _overwrite_line(self, overwrite_vals): + self.ensure_one() + for line in self.line_ids: + vals = overwrite_vals.get("L{}".format(line.sequence), {}) + safe_vals = self._safe_vals(line._name, vals) + line.write(safe_vals) + # STEP 2 def generate_move(self): self.ensure_one() @@ -138,7 +215,7 @@ class AccountMoveTemplateRun(models.TransientModel): def _prepare_move_line(self, line, amount): date_maturity = False if line.payment_term_id: - pterm_list = line.payment_term_id.compute(value=1, date_ref=self.date)[0] + pterm_list = line.payment_term_id.compute(value=1, date_ref=self.date) date_maturity = max(l[0] for l in pterm_list) debit = line.move_line_type == "dr" values = { @@ -165,8 +242,24 @@ class AccountMoveTemplateRun(models.TransientModel): values["tag_ids"] = [(6, 0, atrl_ids.mapped("tag_ids").ids)] if line.tax_repartition_line_id: values["tag_ids"] = [(6, 0, line.tax_repartition_line_id.tag_ids.ids)] + # With overwrite options + overwrite = self._context.get("overwrite", {}) + move_line_vals = overwrite.get("L{}".format(line.sequence), {}) + values.update(move_line_vals) + # Use optional account, when amount is negative + self._update_account_on_negative(line, values) return values + def _update_account_on_negative(self, line, vals): + if not line.opt_account_id: + return + for key in ["debit", "credit"]: + if vals[key] < 0: + ikey = (key == "debit") and "credit" or "debit" + vals["account_id"] = line.opt_account_id.id + vals[ikey] = abs(vals[key]) + vals[key] = 0 + class AccountMoveTemplateLineRun(models.TransientModel): _name = "account.move.template.line.run" diff --git a/account_move_template/wizard/account_move_template_run_view.xml b/account_move_template/wizard/account_move_template_run_view.xml index 29289882e..c8e0d41f0 100644 --- a/account_move_template/wizard/account_move_template_run_view.xml +++ b/account_move_template/wizard/account_move_template_run_view.xml @@ -12,6 +12,13 @@ domain="[('company_id', '=', company_id)]" attrs="{'readonly': [('state', '=', 'set_lines')]}" /> +