2
0

[13.0][IMP] account_move_template, add options to overwirte output lines

This commit is contained in:
Kitti U 2020-11-20 10:59:59 +07:00 committed by Abraham Anes
parent 36b5238751
commit 9dedd88bf1
9 changed files with 330 additions and 4 deletions

View File

@ -17,7 +17,7 @@ class AccountMoveTemplate(models.Model):
string="Company", string="Company",
required=True, required=True,
ondelete="cascade", 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) journal_id = fields.Many2one("account.journal", string="Journal", required=True)
ref = fields.Char(string="Reference", copy=False) ref = fields.Char(string="Reference", copy=False)
@ -159,6 +159,12 @@ class AccountMoveTemplateLine(models.Model):
store=True, store=True,
readonly=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") @api.depends("is_refund", "account_id", "tax_line_id")
def _compute_tax_repartition_line_id(self): def _compute_tax_repartition_line_id(self):

View File

@ -19,3 +19,7 @@ Module Contributors
* Harald Panten <harald.panten@sygel.es> * Harald Panten <harald.panten@sygel.es>
* Valentin Vinagre <valentin.vinagre@sygel.es> * Valentin Vinagre <valentin.vinagre@sygel.es>
* `Ecosoft <http://ecosoft.co.th>`_:
* Kitti U. <kittiu@ecosoft.co.th> (Add context overwrite)

View File

@ -7,3 +7,23 @@ the amount of every input lines.
The journal entry form allows lo load, through a wizard, The journal entry form allows lo load, through a wizard,
the template to use and the amounts to fill. 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'}, }

View File

@ -10,4 +10,5 @@ To use an existing template:
#. Go to *Invoicing / Accounting / Miscellaneous / Create Entry from Template* #. Go to *Invoicing / Accounting / Miscellaneous / Create Entry from Template*
#. Select one of the available templates. #. 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*. #. Complete the entries according to the template and click on the button *Generate Journal Entry*.

View File

@ -1,3 +1,4 @@
# Needs to be re-written because wizard.multi.charts.accounts # Needs to be re-written because wizard.multi.charts.accounts
# doesn't exist any more on v12 # doesn't exist any more on v12
# from . import test_account_move_template # from . import test_account_move_template
from . import test_account_move_template_options

View File

@ -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,
},
],
)

View File

@ -7,6 +7,10 @@
<tree> <tree>
<field name="sequence" /> <field name="sequence" />
<field name="account_id" domain="[('company_id', '=', company_id)]" /> <field name="account_id" domain="[('company_id', '=', company_id)]" />
<field
name="opt_account_id"
domain="[('company_id', '=', company_id)]"
/>
<field name="partner_id" /> <field name="partner_id" />
<field name="name" /> <field name="name" />
<field <field
@ -47,6 +51,10 @@
name="account_id" name="account_id"
domain="[('company_id', '=', company_id)]" domain="[('company_id', '=', company_id)]"
/> />
<field
name="opt_account_id"
domain="[('company_id', '=', company_id)]"
/>
<field name="partner_id" /> <field name="partner_id" />
<field name="payment_term_id" /> <field name="payment_term_id" />
<field name="company_id" invisible="1" /> <field name="company_id" invisible="1" />

View File

@ -1,8 +1,9 @@
# Copyright 2015-2019 See manifest # Copyright 2015-2019 See manifest
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html # 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 import _, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError, ValidationError
from odoo.tools import float_is_zero from odoo.tools import float_is_zero
@ -15,7 +16,7 @@ class AccountMoveTemplateRun(models.TransientModel):
"res.company", "res.company",
required=True, required=True,
readonly=True, readonly=True,
default=lambda self: self.env["res.company"]._company_default_get(), default=lambda self: self.env.company,
) )
partner_id = fields.Many2one( partner_id = fields.Many2one(
"res.partner", "res.partner",
@ -33,6 +34,13 @@ class AccountMoveTemplateRun(models.TransientModel):
readonly=True, readonly=True,
default="select_template", 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): def _prepare_wizard_line(self, tmpl_line):
vals = { vals = {
@ -57,6 +65,8 @@ class AccountMoveTemplateRun(models.TransientModel):
# STEP 1 # STEP 1
def load_lines(self): def load_lines(self):
self.ensure_one() self.ensure_one()
# Verify and get overwrite dict
overwrite_vals = self._get_overwrite_vals()
amtlro = self.env["account.move.template.line.run"] amtlro = self.env["account.move.template.line.run"]
if self.company_id != self.template_id.company_id: if self.company_id != self.template_id.company_id:
raise UserError( raise UserError(
@ -86,8 +96,75 @@ class AccountMoveTemplateRun(models.TransientModel):
action = self.env.ref("account_move_template.account_move_template_run_action") action = self.env.ref("account_move_template.account_move_template_run_action")
result = action.read()[0] result = action.read()[0]
result.update({"res_id": self.id, "context": self.env.context}) 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 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 # STEP 2
def generate_move(self): def generate_move(self):
self.ensure_one() self.ensure_one()
@ -138,7 +215,7 @@ class AccountMoveTemplateRun(models.TransientModel):
def _prepare_move_line(self, line, amount): def _prepare_move_line(self, line, amount):
date_maturity = False date_maturity = False
if line.payment_term_id: 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) date_maturity = max(l[0] for l in pterm_list)
debit = line.move_line_type == "dr" debit = line.move_line_type == "dr"
values = { values = {
@ -165,8 +242,24 @@ class AccountMoveTemplateRun(models.TransientModel):
values["tag_ids"] = [(6, 0, atrl_ids.mapped("tag_ids").ids)] values["tag_ids"] = [(6, 0, atrl_ids.mapped("tag_ids").ids)]
if line.tax_repartition_line_id: if line.tax_repartition_line_id:
values["tag_ids"] = [(6, 0, line.tax_repartition_line_id.tag_ids.ids)] 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 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): class AccountMoveTemplateLineRun(models.TransientModel):
_name = "account.move.template.line.run" _name = "account.move.template.line.run"

View File

@ -12,6 +12,13 @@
domain="[('company_id', '=', company_id)]" domain="[('company_id', '=', company_id)]"
attrs="{'readonly': [('state', '=', 'set_lines')]}" attrs="{'readonly': [('state', '=', 'set_lines')]}"
/> />
<field
name="overwrite"
widget="ace"
options="{'mode': 'python'}"
attrs="{'invisible': [('state', '=', 'set_lines')]}"
placeholder="Add an internal note here..."
/>
<field name="company_id" groups="base.group_multi_company" /> <field name="company_id" groups="base.group_multi_company" />
<field name="date" states="set_lines" /> <field name="date" states="set_lines" />
<field name="journal_id" states="set_lines" /> <field name="journal_id" states="set_lines" />