[IMP] account_loan: Allow to increase the amount of the loan
This commit is contained in:
parent
7781a19940
commit
90a5bf5353
@ -9,6 +9,7 @@
|
||||
"category": "Accounting",
|
||||
"depends": ["account"],
|
||||
"data": [
|
||||
"wizards/account_loan_increase_amount.xml",
|
||||
"data/ir_sequence_data.xml",
|
||||
"security/ir.model.access.csv",
|
||||
"security/account_loan_security.xml",
|
||||
|
@ -238,34 +238,38 @@ class AccountLoanLine(models.Model):
|
||||
+ self.interests_amount
|
||||
)
|
||||
|
||||
def _move_vals(self):
|
||||
def _move_vals(self, journal=False, account=False):
|
||||
return {
|
||||
"loan_line_id": self.id,
|
||||
"loan_id": self.loan_id.id,
|
||||
"date": self.date,
|
||||
"ref": self.name,
|
||||
"journal_id": self.loan_id.journal_id.id,
|
||||
"line_ids": [Command.create(vals) for vals in self._move_line_vals()],
|
||||
"journal_id": (journal and journal.id) or self.loan_id.journal_id.id,
|
||||
"line_ids": [
|
||||
Command.create(vals) for vals in self._move_line_vals(account=account)
|
||||
],
|
||||
}
|
||||
|
||||
def _move_line_vals(self):
|
||||
def _move_line_vals(self, account=False):
|
||||
vals = []
|
||||
partner = self.loan_id.partner_id.with_company(self.loan_id.company_id)
|
||||
vals.append(
|
||||
{
|
||||
"account_id": partner.property_account_payable_id.id,
|
||||
"account_id": (account and account.id)
|
||||
or partner.property_account_payable_id.id,
|
||||
"partner_id": partner.id,
|
||||
"credit": self.payment_amount,
|
||||
"debit": 0,
|
||||
}
|
||||
)
|
||||
vals.append(
|
||||
{
|
||||
"account_id": self.loan_id.interest_expenses_account_id.id,
|
||||
"credit": 0,
|
||||
"debit": self.interests_amount,
|
||||
}
|
||||
)
|
||||
if self.interests_amount:
|
||||
vals.append(
|
||||
{
|
||||
"account_id": self.loan_id.interest_expenses_account_id.id,
|
||||
"credit": 0,
|
||||
"debit": self.interests_amount,
|
||||
}
|
||||
)
|
||||
vals.append(
|
||||
{
|
||||
"account_id": self.loan_id.short_term_loan_account_id.id,
|
||||
@ -326,7 +330,7 @@ class AccountLoanLine(models.Model):
|
||||
)
|
||||
return vals
|
||||
|
||||
def _generate_move(self):
|
||||
def _generate_move(self, journal=False, account=False):
|
||||
"""
|
||||
Computes and post the moves of loans
|
||||
:return: list of account.move generated
|
||||
@ -338,7 +342,9 @@ class AccountLoanLine(models.Model):
|
||||
lambda r: r.date < record.date and not r.move_ids
|
||||
):
|
||||
raise UserError(_("Some moves must be created first"))
|
||||
move = self.env["account.move"].create(record._move_vals())
|
||||
move = self.env["account.move"].create(
|
||||
record._move_vals(journal=journal, account=account)
|
||||
)
|
||||
move.action_post()
|
||||
res.append(move.id)
|
||||
return res
|
||||
@ -372,6 +378,9 @@ class AccountLoanLine(models.Model):
|
||||
for line in invoice.invoice_line_ids:
|
||||
line.tax_ids = line._get_computed_taxes()
|
||||
invoice.flush_recordset()
|
||||
invoice.filtered(
|
||||
lambda m: m.currency_id.round(m.amount_total) < 0
|
||||
).action_switch_invoice_into_refund_credit_note()
|
||||
if record.loan_id.post_invoice:
|
||||
invoice.action_post()
|
||||
if (
|
||||
|
@ -5,4 +5,5 @@ access_account_loan_line,account.loan.line,model_account_loan_line,account.group
|
||||
access_account_loan_line_manager,account.loan.line,model_account_loan_line,account.group_account_manager,1,1,1,1
|
||||
access_account_loan_generate_wizard,access_account_loan_generate_wizard,model_account_loan_generate_wizard,account.group_account_manager,1,1,1,1
|
||||
access_account_loan_pay_amount,access_account_loan_pay_amount,model_account_loan_pay_amount,account.group_account_manager,1,1,1,1
|
||||
access_account_loan_increase_amount,access_account_loan_increase_amount,model_account_loan_increase_amount,account.group_account_manager,1,1,1,1
|
||||
access_account_loan_post,access_account_loan_post,model_account_loan_post,account.group_account_manager,1,1,1,1
|
||||
|
|
@ -103,6 +103,52 @@ class TestLoan(TransactionCase):
|
||||
self.assertEqual(line_1.principal_amount, 0)
|
||||
self.assertEqual(line_end.principal_amount, 500000)
|
||||
|
||||
def test_increase_amount_validation(self):
|
||||
amount = 10000
|
||||
periods = 24
|
||||
loan = self.create_loan("fixed-annuity", amount, 1, periods)
|
||||
self.assertTrue(loan.line_ids)
|
||||
self.assertEqual(len(loan.line_ids), periods)
|
||||
line = loan.line_ids.filtered(lambda r: r.sequence == 1)
|
||||
self.assertAlmostEqual(
|
||||
-numpy_financial.pmt(1 / 100 / 12, 24, 10000), line.payment_amount, 2
|
||||
)
|
||||
self.assertEqual(line.long_term_principal_amount, 0)
|
||||
loan.long_term_loan_account_id = self.lt_loan_account
|
||||
loan.compute_lines()
|
||||
line = loan.line_ids.filtered(lambda r: r.sequence == 1)
|
||||
self.assertGreater(line.long_term_principal_amount, 0)
|
||||
self.post(loan)
|
||||
self.assertTrue(loan.start_date)
|
||||
line = loan.line_ids.filtered(lambda r: r.sequence == 1)
|
||||
self.assertTrue(line)
|
||||
self.assertFalse(line.move_ids)
|
||||
wzd = self.env["account.loan.generate.wizard"].create({})
|
||||
action = wzd.run()
|
||||
self.assertTrue(action)
|
||||
self.assertFalse(wzd.run())
|
||||
self.assertTrue(line.move_ids)
|
||||
self.assertIn(line.move_ids.id, action["domain"][0][2])
|
||||
self.assertTrue(line.move_ids)
|
||||
self.assertEqual(line.move_ids.state, "posted")
|
||||
with self.assertRaises(UserError):
|
||||
self.env["account.loan.increase.amount"].with_context(
|
||||
default_loan_id=loan.id
|
||||
).create(
|
||||
{
|
||||
"amount": (amount - amount / periods) / 2,
|
||||
"date": line.date + relativedelta(months=-1),
|
||||
}
|
||||
).run()
|
||||
with self.assertRaises(UserError):
|
||||
self.env["account.loan.increase.amount"].with_context(
|
||||
default_loan_id=loan.id
|
||||
).create({"amount": 0, "date": line.date}).run()
|
||||
with self.assertRaises(UserError):
|
||||
self.env["account.loan.increase.amount"].with_context(
|
||||
default_loan_id=loan.id
|
||||
).create({"amount": -100, "date": line.date}).run()
|
||||
|
||||
def test_pay_amount_validation(self):
|
||||
amount = 10000
|
||||
periods = 24
|
||||
@ -132,26 +178,125 @@ class TestLoan(TransactionCase):
|
||||
self.assertTrue(line.move_ids)
|
||||
self.assertEqual(line.move_ids.state, "posted")
|
||||
with self.assertRaises(UserError):
|
||||
self.env["account.loan.pay.amount"].create(
|
||||
self.env["account.loan.pay.amount"].with_context(
|
||||
default_loan_id=loan.id
|
||||
).create(
|
||||
{
|
||||
"loan_id": loan.id,
|
||||
"amount": (amount - amount / periods) / 2,
|
||||
"fees": 100,
|
||||
"date": line.date + relativedelta(months=-1),
|
||||
}
|
||||
).run()
|
||||
with self.assertRaises(UserError):
|
||||
self.env["account.loan.pay.amount"].create(
|
||||
{"loan_id": loan.id, "amount": amount, "fees": 100, "date": line.date}
|
||||
).run()
|
||||
self.env["account.loan.pay.amount"].with_context(
|
||||
default_loan_id=loan.id
|
||||
).create({"amount": amount, "fees": 100, "date": line.date}).run()
|
||||
with self.assertRaises(UserError):
|
||||
self.env["account.loan.pay.amount"].create(
|
||||
{"loan_id": loan.id, "amount": 0, "fees": 100, "date": line.date}
|
||||
).run()
|
||||
self.env["account.loan.pay.amount"].with_context(
|
||||
default_loan_id=loan.id
|
||||
).create({"amount": 0, "fees": 100, "date": line.date}).run()
|
||||
with self.assertRaises(UserError):
|
||||
self.env["account.loan.pay.amount"].create(
|
||||
{"loan_id": loan.id, "amount": -100, "fees": 100, "date": line.date}
|
||||
).run()
|
||||
self.env["account.loan.pay.amount"].with_context(
|
||||
default_loan_id=loan.id
|
||||
).create({"amount": -100, "fees": 100, "date": line.date}).run()
|
||||
|
||||
def test_increase_amount_loan(self):
|
||||
amount = 10000
|
||||
periods = 24
|
||||
loan = self.create_loan("fixed-annuity", amount, 1, periods)
|
||||
self.assertTrue(loan.line_ids)
|
||||
self.assertEqual(len(loan.line_ids), periods)
|
||||
line = loan.line_ids.filtered(lambda r: r.sequence == 1)
|
||||
self.assertAlmostEqual(
|
||||
-numpy_financial.pmt(1 / 100 / 12, 24, 10000), line.payment_amount, 2
|
||||
)
|
||||
self.assertEqual(line.long_term_principal_amount, 0)
|
||||
loan.long_term_loan_account_id = self.lt_loan_account
|
||||
loan.compute_lines()
|
||||
line = loan.line_ids.filtered(lambda r: r.sequence == 1)
|
||||
self.assertGreater(line.long_term_principal_amount, 0)
|
||||
self.post(loan)
|
||||
self.assertTrue(loan.start_date)
|
||||
line = loan.line_ids.filtered(lambda r: r.sequence == 1)
|
||||
self.assertTrue(line)
|
||||
self.assertFalse(line.move_ids)
|
||||
wzd = self.env["account.loan.generate.wizard"].create({})
|
||||
action = wzd.run()
|
||||
self.assertTrue(action)
|
||||
self.assertFalse(wzd.run())
|
||||
self.assertTrue(line.move_ids)
|
||||
self.assertIn(line.move_ids.id, action["domain"][0][2])
|
||||
self.assertTrue(line.move_ids)
|
||||
self.assertEqual(line.move_ids.state, "posted")
|
||||
pending_principal_amount = loan.pending_principal_amount
|
||||
action = (
|
||||
self.env["account.loan.increase.amount"]
|
||||
.with_context(default_loan_id=loan.id)
|
||||
.create(
|
||||
{
|
||||
"amount": 1000,
|
||||
"date": line.date,
|
||||
}
|
||||
)
|
||||
.run()
|
||||
)
|
||||
new_move = self.env[action["res_model"]].search(action["domain"])
|
||||
new_move.ensure_one()
|
||||
self.assertFalse(new_move.is_invoice())
|
||||
self.assertEqual(loan, new_move.loan_id)
|
||||
self.assertEqual(loan.pending_principal_amount, pending_principal_amount + 1000)
|
||||
|
||||
def test_increase_amount_leasing(self):
|
||||
amount = 10000
|
||||
periods = 24
|
||||
loan = self.create_loan("fixed-annuity", amount, 1, periods)
|
||||
self.assertTrue(loan.line_ids)
|
||||
self.assertEqual(len(loan.line_ids), periods)
|
||||
line = loan.line_ids.filtered(lambda r: r.sequence == 1)
|
||||
self.assertAlmostEqual(
|
||||
-numpy_financial.pmt(1 / 100 / 12, 24, 10000), line.payment_amount, 2
|
||||
)
|
||||
self.assertEqual(line.long_term_principal_amount, 0)
|
||||
loan.is_leasing = True
|
||||
loan.long_term_loan_account_id = self.lt_loan_account
|
||||
loan.compute_lines()
|
||||
line = loan.line_ids.filtered(lambda r: r.sequence == 1)
|
||||
self.assertGreater(line.long_term_principal_amount, 0)
|
||||
self.post(loan)
|
||||
self.assertTrue(loan.start_date)
|
||||
line = loan.line_ids.filtered(lambda r: r.sequence == 1)
|
||||
self.assertTrue(line)
|
||||
self.assertFalse(line.move_ids)
|
||||
wzd = self.env["account.loan.generate.wizard"].create(
|
||||
{
|
||||
"date": fields.date.today() + relativedelta(days=1),
|
||||
"loan_type": "leasing",
|
||||
}
|
||||
)
|
||||
action = wzd.run()
|
||||
self.assertTrue(action)
|
||||
self.assertFalse(wzd.run())
|
||||
self.assertTrue(line.move_ids)
|
||||
self.assertIn(line.move_ids.id, action["domain"][0][2])
|
||||
self.assertTrue(line.move_ids)
|
||||
self.assertEqual(line.move_ids.state, "posted")
|
||||
pending_principal_amount = loan.pending_principal_amount
|
||||
action = (
|
||||
self.env["account.loan.increase.amount"]
|
||||
.with_context(default_loan_id=loan.id)
|
||||
.create(
|
||||
{
|
||||
"amount": 1000,
|
||||
"date": line.date,
|
||||
}
|
||||
)
|
||||
.run()
|
||||
)
|
||||
new_move = self.env[action["res_model"]].search(action["domain"])
|
||||
new_move.ensure_one()
|
||||
self.assertFalse(new_move.is_invoice())
|
||||
self.assertEqual(loan, new_move.loan_id)
|
||||
self.assertEqual(loan.pending_principal_amount, pending_principal_amount + 1000)
|
||||
|
||||
def test_fixed_annuity_begin_loan(self):
|
||||
amount = 10000
|
||||
|
@ -66,12 +66,29 @@
|
||||
<button
|
||||
name="%(account_loan_pay_amount_action)d"
|
||||
class="oe_stat_button"
|
||||
icon="fa-usd"
|
||||
icon="fa-arrow-down"
|
||||
attrs="{'invisible': [('state', '!=', 'posted')]}"
|
||||
type="action"
|
||||
string="Pay amount"
|
||||
groups="account.group_account_manager"
|
||||
/>
|
||||
>
|
||||
<div class="o_field_widget o_stat_info">
|
||||
<span class="o_stat_text">Deduct</span>
|
||||
<span class="o_stat_text">Debt</span>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
name="%(account_loan_increase_amount_act_window)d"
|
||||
class="oe_stat_button"
|
||||
icon="fa-arrow-up"
|
||||
attrs="{'invisible': [('state', '!=', 'posted')]}"
|
||||
type="action"
|
||||
groups="account.group_account_manager"
|
||||
>
|
||||
<div class="o_field_widget o_stat_info">
|
||||
<span class="o_stat_text">Increase</span>
|
||||
<span class="o_stat_text">Debt</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<h1>
|
||||
<field name="name" />
|
||||
|
@ -3,3 +3,4 @@
|
||||
from . import account_loan_generate_entries
|
||||
from . import account_loan_pay_amount
|
||||
from . import account_loan_post
|
||||
from . import account_loan_increase_amount
|
||||
|
113
account_loan/wizards/account_loan_increase_amount.py
Normal file
113
account_loan/wizards/account_loan_increase_amount.py
Normal file
@ -0,0 +1,113 @@
|
||||
# Copyright 2023 Dixmit
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class AccountLoanIncreaseAmount(models.TransientModel):
|
||||
_name = "account.loan.increase.amount"
|
||||
_description = "Increase the debt of a loan"
|
||||
|
||||
@api.model
|
||||
def _default_journal_id(self):
|
||||
loan_id = self.env.context.get("default_loan_id")
|
||||
if loan_id:
|
||||
return self.env["account.loan"].browse(loan_id).journal_id.id
|
||||
|
||||
@api.model
|
||||
def _default_account_id(self):
|
||||
loan_id = self.env.context.get("default_loan_id")
|
||||
if loan_id:
|
||||
loan = self.env["account.loan"].browse(loan_id)
|
||||
if loan.is_leasing:
|
||||
return loan.leased_asset_account_id.id
|
||||
else:
|
||||
return loan.partner_id.with_company(
|
||||
loan.company_id
|
||||
).property_account_receivable_id.id
|
||||
|
||||
journal_id = fields.Many2one(
|
||||
"account.journal", required=True, default=lambda r: r._default_journal_id()
|
||||
)
|
||||
account_id = fields.Many2one(
|
||||
"account.account", required=True, default=lambda r: r._default_account_id()
|
||||
)
|
||||
loan_id = fields.Many2one(
|
||||
"account.loan",
|
||||
required=True,
|
||||
readonly=True,
|
||||
)
|
||||
currency_id = fields.Many2one(
|
||||
"res.currency", related="loan_id.currency_id", readonly=True
|
||||
)
|
||||
date = fields.Date(required=True, default=fields.Date.today())
|
||||
amount = fields.Monetary(
|
||||
currency_field="currency_id",
|
||||
string="Amount to reduce from Principal",
|
||||
)
|
||||
|
||||
def new_line_vals(self, sequence):
|
||||
return {
|
||||
"loan_id": self.loan_id.id,
|
||||
"sequence": sequence,
|
||||
"payment_amount": -self.amount,
|
||||
"rate": 0,
|
||||
"interests_amount": 0,
|
||||
"date": self.date,
|
||||
}
|
||||
|
||||
def run(self):
|
||||
self.ensure_one()
|
||||
if self.loan_id.is_leasing:
|
||||
if self.loan_id.line_ids.filtered(
|
||||
lambda r: r.date <= self.date and not r.move_ids
|
||||
):
|
||||
raise UserError(_("Some invoices are not created"))
|
||||
if self.loan_id.line_ids.filtered(
|
||||
lambda r: r.date > self.date and r.move_ids
|
||||
):
|
||||
raise UserError(_("Some future invoices already exists"))
|
||||
else:
|
||||
if self.loan_id.line_ids.filtered(
|
||||
lambda r: r.date < self.date and not r.move_ids
|
||||
):
|
||||
raise UserError(_("Some moves are not created"))
|
||||
if self.loan_id.line_ids.filtered(
|
||||
lambda r: r.date > self.date and r.move_ids
|
||||
):
|
||||
raise UserError(_("Some future moves already exists"))
|
||||
lines = self.loan_id.line_ids.filtered(lambda r: r.date > self.date).sorted(
|
||||
"sequence", reverse=True
|
||||
)
|
||||
sequence = min(lines.mapped("sequence"))
|
||||
for line in lines:
|
||||
line.sequence += 1
|
||||
line.flush_recordset()
|
||||
old_line = lines.filtered(lambda r: r.sequence == sequence + 1)
|
||||
pending = old_line.pending_principal_amount
|
||||
if self.loan_id.currency_id.compare_amounts(self.amount, 0) <= 0:
|
||||
raise UserError(_("Amount cannot be less than zero"))
|
||||
self.loan_id.periods += 1
|
||||
self.loan_id.fixed_periods = self.loan_id.periods - sequence
|
||||
self.loan_id.fixed_loan_amount = pending - self.amount
|
||||
new_line = self.env["account.loan.line"].create(self.new_line_vals(sequence))
|
||||
new_line.long_term_pending_principal_amount = (
|
||||
old_line.long_term_pending_principal_amount
|
||||
)
|
||||
amount = self.loan_id.loan_amount
|
||||
for line in self.loan_id.line_ids.sorted("sequence"):
|
||||
if line.move_ids:
|
||||
amount = line.final_pending_principal_amount
|
||||
else:
|
||||
line.pending_principal_amount = amount
|
||||
if line.sequence != sequence:
|
||||
line.rate = self.loan_id.rate_period
|
||||
line._check_amount()
|
||||
amount -= line.payment_amount - line.interests_amount
|
||||
if self.loan_id.long_term_loan_account_id:
|
||||
self.loan_id._check_long_term_principal_amount()
|
||||
if self.loan_id.currency_id.compare_amounts(pending, self.amount) == 0:
|
||||
self.loan_id.write({"state": "cancelled"})
|
||||
new_line._generate_move(journal=self.journal_id, account=self.account_id)
|
||||
return new_line.view_account_values()
|
40
account_loan/wizards/account_loan_increase_amount.xml
Normal file
40
account_loan/wizards/account_loan_increase_amount.xml
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!-- Copyright 2023 Dixmit
|
||||
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="account_loan_increase_amount_form_view">
|
||||
<field name="name">account.loan.increase.amount.form (in account_loan)</field>
|
||||
<field name="model">account.loan.increase.amount</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<field name="loan_id" readonly="True" />
|
||||
<field name="date" />
|
||||
<field name="amount" />
|
||||
<field name="currency_id" />
|
||||
</group>
|
||||
<footer>
|
||||
<button
|
||||
name="run"
|
||||
string="Run"
|
||||
type="object"
|
||||
class="oe_highlight"
|
||||
/>
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="account_loan_increase_amount_act_window">
|
||||
<field name="name">Increase Amount</field>
|
||||
<field name="res_model">account.loan.increase.amount</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="context">{'default_loan_id': active_id}</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
|
||||
</odoo>
|
@ -57,12 +57,15 @@ class AccountLoan(models.TransientModel):
|
||||
lambda r: r.date > self.date and r.move_ids
|
||||
):
|
||||
raise UserError(_("Some future invoices already exists"))
|
||||
if self.loan_id.line_ids.filtered(
|
||||
lambda r: r.date < self.date and not r.move_ids
|
||||
):
|
||||
raise UserError(_("Some moves are not created"))
|
||||
if self.loan_id.line_ids.filtered(lambda r: r.date > self.date and r.move_ids):
|
||||
raise UserError(_("Some future moves already exists"))
|
||||
else:
|
||||
if self.loan_id.line_ids.filtered(
|
||||
lambda r: r.date < self.date and not r.move_ids
|
||||
):
|
||||
raise UserError(_("Some moves are not created"))
|
||||
if self.loan_id.line_ids.filtered(
|
||||
lambda r: r.date > self.date and r.move_ids
|
||||
):
|
||||
raise UserError(_("Some future moves already exists"))
|
||||
lines = self.loan_id.line_ids.filtered(lambda r: r.date > self.date).sorted(
|
||||
"sequence", reverse=True
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user