2
0

[IMP] account_loan: black,isort,prettier

This commit is contained in:
Alba Riera 2021-02-23 11:00:15 +01:00 committed by Enric Tobella
parent 6a2179377e
commit e6972252f5
16 changed files with 926 additions and 961 deletions

View File

@ -7,23 +7,17 @@
"website": "http://github.com/OCA/account-financial-tools",
"license": "AGPL-3",
"category": "Accounting",
"depends": [
"account"
],
"depends": ["account"],
"data": [
'data/ir_sequence_data.xml',
'security/ir.model.access.csv',
'security/account_loan_security.xml',
'wizard/account_loan_generate_entries_view.xml',
'wizard/account_loan_pay_amount_view.xml',
'wizard/account_loan_post_view.xml',
'views/account_loan_view.xml',
'views/account_move_view.xml',
"data/ir_sequence_data.xml",
"security/ir.model.access.csv",
"security/account_loan_security.xml",
"wizard/account_loan_generate_entries_view.xml",
"wizard/account_loan_pay_amount_view.xml",
"wizard/account_loan_post_view.xml",
"views/account_loan_view.xml",
"views/account_move_view.xml",
],
'installable': True,
'external_dependencies': {
'python': [
'numpy',
],
},
"installable": True,
"external_dependencies": {"python": ["numpy",],},
}

View File

@ -1,18 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright 2017 Eficent Business and IT Consulting Services, S.L.
Copyright 2017 Creu Blanca
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
-->
<odoo>
<record id="seq_account_loan" model="ir.sequence">
<field name="name">Account loan sequence</field>
<field name="code">account.loan</field>
<field name="prefix">ACL</field>
<field name="padding">6</field>
</record>
</odoo>

View File

@ -5,18 +5,13 @@ from odoo import api, fields, models
class AccountInvoice(models.Model):
_inherit = 'account.invoice'
_inherit = "account.invoice"
loan_line_id = fields.Many2one(
'account.loan.line',
readonly=True,
ondelete='restrict',
"account.loan.line", readonly=True, ondelete="restrict",
)
loan_id = fields.Many2one(
'account.loan',
readonly=True,
store=True,
ondelete='restrict',
"account.loan", readonly=True, store=True, ondelete="restrict",
)
@api.multi
@ -24,18 +19,27 @@ class AccountInvoice(models.Model):
vals = super().finalize_invoice_move_lines(move_lines)
if self.loan_line_id:
ll = self.loan_line_id
if (
ll.long_term_loan_account_id and
ll.long_term_principal_amount != 0
):
vals.append((0, 0, {
'account_id': ll.loan_id.short_term_loan_account_id.id,
'credit': ll.long_term_principal_amount,
'debit': 0,
}))
vals.append((0, 0, {
'account_id': ll.long_term_loan_account_id.id,
'credit': 0,
'debit': ll.long_term_principal_amount,
}))
if ll.long_term_loan_account_id and ll.long_term_principal_amount != 0:
vals.append(
(
0,
0,
{
"account_id": ll.loan_id.short_term_loan_account_id.id,
"credit": ll.long_term_principal_amount,
"debit": 0,
},
)
)
vals.append(
(
0,
0,
{
"account_id": ll.long_term_loan_account_id.id,
"credit": 0,
"debit": ll.long_term_principal_amount,
},
)
)
return vals

View File

@ -1,12 +1,13 @@
# Copyright 2018 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models
from datetime import datetime
from dateutil.relativedelta import relativedelta
import logging
import math
from datetime import datetime
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models
_logger = logging.getLogger(__name__)
try:
@ -16,12 +17,12 @@ except (ImportError, IOError) as err:
class AccountLoan(models.Model):
_name = 'account.loan'
_description = 'Loan'
_inherit = ['mail.thread', 'mail.activity.mixin']
_name = "account.loan"
_description = "Loan"
_inherit = ["mail.thread", "mail.activity.mixin"]
def _default_company(self):
force_company = self._context.get('force_company')
force_company = self._context.get("force_company")
if not force_company:
return self.env.user.company_id.id
return force_company
@ -30,243 +31,218 @@ class AccountLoan(models.Model):
copy=False,
required=True,
readonly=True,
default='/',
states={'draft': [('readonly', False)]},
default="/",
states={"draft": [("readonly", False)]},
)
partner_id = fields.Many2one(
'res.partner',
"res.partner",
required=True,
string='Lender',
help='Company or individual that lends the money at an interest rate.',
string="Lender",
help="Company or individual that lends the money at an interest rate.",
readonly=True,
states={'draft': [('readonly', False)]},
states={"draft": [("readonly", False)]},
)
company_id = fields.Many2one(
'res.company',
"res.company",
required=True,
default=_default_company,
readonly=True,
states={'draft': [('readonly', False)]},
states={"draft": [("readonly", False)]},
)
state = fields.Selection([
('draft', 'Draft'),
('posted', 'Posted'),
('cancelled', 'Cancelled'),
('closed', 'Closed'),
], required=True, copy=False, default='draft')
line_ids = fields.One2many(
'account.loan.line',
readonly=True,
inverse_name='loan_id',
state = fields.Selection(
[
("draft", "Draft"),
("posted", "Posted"),
("cancelled", "Cancelled"),
("closed", "Closed"),
],
required=True,
copy=False,
default="draft",
)
line_ids = fields.One2many(
"account.loan.line", readonly=True, inverse_name="loan_id", copy=False,
)
periods = fields.Integer(
required=True,
readonly=True,
states={'draft': [('readonly', False)]},
help='Number of periods that the loan will last',
states={"draft": [("readonly", False)]},
help="Number of periods that the loan will last",
)
method_period = fields.Integer(
string='Period Length',
string="Period Length",
default=1,
help="State here the time between 2 depreciations, in months",
required=True,
readonly=True,
states={'draft': [('readonly', False)]},
states={"draft": [("readonly", False)]},
)
start_date = fields.Date(
help='Start of the moves',
help="Start of the moves",
readonly=True,
states={'draft': [('readonly', False)]},
states={"draft": [("readonly", False)]},
copy=False,
)
rate = fields.Float(
required=True,
default=0.0,
digits=(8, 6),
help='Currently applied rate',
track_visibility='always',
help="Currently applied rate",
track_visibility="always",
)
rate_period = fields.Float(
compute='_compute_rate_period', digits=(8, 6),
help='Real rate that will be applied on each period',
compute="_compute_rate_period",
digits=(8, 6),
help="Real rate that will be applied on each period",
)
rate_type = fields.Selection(
[
('napr', 'Nominal APR'),
('ear', 'EAR'),
('real', 'Real rate'),
],
[("napr", "Nominal APR"), ("ear", "EAR"), ("real", "Real rate"),],
required=True,
help='Method of computation of the applied rate',
default='napr',
help="Method of computation of the applied rate",
default="napr",
readonly=True,
states={'draft': [('readonly', False)]},
states={"draft": [("readonly", False)]},
)
loan_type = fields.Selection(
[
('fixed-annuity', 'Fixed Annuity'),
('fixed-annuity-begin', 'Fixed Annuity Begin'),
('fixed-principal', 'Fixed Principal'),
('interest', 'Only interest'),
("fixed-annuity", "Fixed Annuity"),
("fixed-annuity-begin", "Fixed Annuity Begin"),
("fixed-principal", "Fixed Principal"),
("interest", "Only interest"),
],
required=True,
help='Method of computation of the period annuity',
help="Method of computation of the period annuity",
readonly=True,
states={'draft': [('readonly', False)]},
default='fixed-annuity'
states={"draft": [("readonly", False)]},
default="fixed-annuity",
)
fixed_amount = fields.Monetary(
currency_field='currency_id',
compute='_compute_fixed_amount',
currency_field="currency_id", compute="_compute_fixed_amount",
)
fixed_loan_amount = fields.Monetary(
currency_field='currency_id',
readonly=True,
copy=False,
default=0,
)
fixed_periods = fields.Integer(
readonly=True,
copy=False,
default=0,
currency_field="currency_id", readonly=True, copy=False, default=0,
)
fixed_periods = fields.Integer(readonly=True, copy=False, default=0,)
loan_amount = fields.Monetary(
currency_field='currency_id',
currency_field="currency_id",
required=True,
readonly=True,
states={'draft': [('readonly', False)]},
states={"draft": [("readonly", False)]},
)
residual_amount = fields.Monetary(
currency_field='currency_id',
default=0.,
currency_field="currency_id",
default=0.0,
required=True,
readonly=True,
states={'draft': [('readonly', False)]},
help='Residual amount of the lease that must be payed on the end in '
'order to acquire the asset',
states={"draft": [("readonly", False)]},
help="Residual amount of the lease that must be payed on the end in "
"order to acquire the asset",
)
round_on_end = fields.Boolean(
default=False,
help='When checked, the differences will be applied on the last period'
', if it is unchecked, the annuity will be recalculated on each '
'period.',
help="When checked, the differences will be applied on the last period"
", if it is unchecked, the annuity will be recalculated on each "
"period.",
readonly=True,
states={'draft': [('readonly', False)]},
states={"draft": [("readonly", False)]},
)
payment_on_first_period = fields.Boolean(
default=False,
readonly=True,
states={'draft': [('readonly', False)]},
help='When checked, the first payment will be on start date',
states={"draft": [("readonly", False)]},
help="When checked, the first payment will be on start date",
)
currency_id = fields.Many2one(
'res.currency',
compute='_compute_currency',
readonly=True,
"res.currency", compute="_compute_currency", readonly=True,
)
journal_type = fields.Char(compute='_compute_journal_type')
journal_type = fields.Char(compute="_compute_journal_type")
journal_id = fields.Many2one(
'account.journal',
"account.journal",
domain="[('company_id', '=', company_id),('type', '=', journal_type)]",
required=True,
readonly=True,
states={'draft': [('readonly', False)]},
states={"draft": [("readonly", False)]},
)
short_term_loan_account_id = fields.Many2one(
'account.account',
"account.account",
domain="[('company_id', '=', company_id)]",
string='Short term account',
help='Account that will contain the pending amount on short term',
string="Short term account",
help="Account that will contain the pending amount on short term",
required=True,
readonly=True,
states={'draft': [('readonly', False)]},
states={"draft": [("readonly", False)]},
)
long_term_loan_account_id = fields.Many2one(
'account.account',
string='Long term account',
help='Account that will contain the pending amount on Long term',
"account.account",
string="Long term account",
help="Account that will contain the pending amount on Long term",
domain="[('company_id', '=', company_id)]",
readonly=True,
states={'draft': [('readonly', False)]},
states={"draft": [("readonly", False)]},
)
interest_expenses_account_id = fields.Many2one(
'account.account',
"account.account",
domain="[('company_id', '=', company_id)]",
string='Interests account',
help='Account where the interests will be assigned to',
string="Interests account",
help="Account where the interests will be assigned to",
required=True,
readonly=True,
states={'draft': [('readonly', False)]},
states={"draft": [("readonly", False)]},
)
is_leasing = fields.Boolean(
default=False,
readonly=True,
states={'draft': [('readonly', False)]},
default=False, readonly=True, states={"draft": [("readonly", False)]},
)
leased_asset_account_id = fields.Many2one(
'account.account',
"account.account",
domain="[('company_id', '=', company_id)]",
readonly=True,
states={'draft': [('readonly', False)]},
states={"draft": [("readonly", False)]},
)
product_id = fields.Many2one(
'product.product',
string='Loan product',
help='Product where the amount of the loan will be assigned when the '
'invoice is created',
"product.product",
string="Loan product",
help="Product where the amount of the loan will be assigned when the "
"invoice is created",
)
interests_product_id = fields.Many2one(
'product.product',
string='Interest product',
help='Product where the amount of interests will be assigned when the '
'invoice is created',
)
move_ids = fields.One2many(
'account.move',
copy=False,
inverse_name='loan_id'
"product.product",
string="Interest product",
help="Product where the amount of interests will be assigned when the "
"invoice is created",
)
move_ids = fields.One2many("account.move", copy=False, inverse_name="loan_id")
pending_principal_amount = fields.Monetary(
currency_field='currency_id',
compute='_compute_total_amounts',
currency_field="currency_id", compute="_compute_total_amounts",
)
payment_amount = fields.Monetary(
currency_field='currency_id',
string='Total payed amount',
compute='_compute_total_amounts',
currency_field="currency_id",
string="Total payed amount",
compute="_compute_total_amounts",
)
interests_amount = fields.Monetary(
currency_field='currency_id',
string='Total interests payed',
compute='_compute_total_amounts',
currency_field="currency_id",
string="Total interests payed",
compute="_compute_total_amounts",
)
post_invoice = fields.Boolean(
default=True,
help='Invoices will be posted automatically'
default=True, help="Invoices will be posted automatically"
)
_sql_constraints = [
('name_uniq', 'unique(name, company_id)',
'Loan name must be unique'),
("name_uniq", "unique(name, company_id)", "Loan name must be unique"),
]
@api.depends('line_ids', 'currency_id', 'loan_amount')
@api.depends("line_ids", "currency_id", "loan_amount")
def _compute_total_amounts(self):
for record in self:
lines = record.line_ids.filtered(lambda r: r.move_ids)
record.payment_amount = sum(
lines.mapped('payment_amount')) or 0.
record.interests_amount = sum(
lines.mapped('interests_amount')) or 0.
record.payment_amount = sum(lines.mapped("payment_amount")) or 0.0
record.interests_amount = sum(lines.mapped("interests_amount")) or 0.0
record.pending_principal_amount = (
record.loan_amount -
record.payment_amount +
record.interests_amount
record.loan_amount - record.payment_amount + record.interests_amount
)
@api.depends('rate_period', 'fixed_loan_amount', 'fixed_periods',
'currency_id')
@api.depends("rate_period", "fixed_loan_amount", "fixed_periods", "currency_id")
def _compute_fixed_amount(self):
"""
Computes the fixed amount in order to be used if round_on_end is
@ -275,25 +251,29 @@ class AccountLoan(models.Model):
:return:
"""
for record in self:
if record.loan_type == 'fixed-annuity':
record.fixed_amount = - record.currency_id.round(numpy.pmt(
record.loan_rate() / 100,
record.fixed_periods,
record.fixed_loan_amount,
-record.residual_amount
))
elif record.loan_type == 'fixed-annuity-begin':
record.fixed_amount = - record.currency_id.round(numpy.pmt(
record.loan_rate() / 100,
record.fixed_periods,
record.fixed_loan_amount,
-record.residual_amount,
when='begin'
))
elif record.loan_type == 'fixed-principal':
if record.loan_type == "fixed-annuity":
record.fixed_amount = -record.currency_id.round(
numpy.pmt(
record.loan_rate() / 100,
record.fixed_periods,
record.fixed_loan_amount,
-record.residual_amount,
)
)
elif record.loan_type == "fixed-annuity-begin":
record.fixed_amount = -record.currency_id.round(
numpy.pmt(
record.loan_rate() / 100,
record.fixed_periods,
record.fixed_loan_amount,
-record.residual_amount,
when="begin",
)
)
elif record.loan_type == "fixed-principal":
record.fixed_amount = record.currency_id.round(
(record.fixed_loan_amount - record.residual_amount) /
record.fixed_periods
(record.fixed_loan_amount - record.residual_amount)
/ record.fixed_periods
)
else:
record.fixed_amount = 0.0
@ -307,57 +287,58 @@ class AccountLoan(models.Model):
:param method_period: Number of months between payments
:return:
"""
if rate_type == 'napr':
if rate_type == "napr":
return rate / 12 * method_period
if rate_type == 'ear':
if rate_type == "ear":
return math.pow(1 + rate, method_period / 12) - 1
return rate
@api.depends('rate', 'method_period', 'rate_type')
@api.depends("rate", "method_period", "rate_type")
def _compute_rate_period(self):
for record in self:
record.rate_period = record.loan_rate()
def loan_rate(self):
return self.compute_rate(
self.rate, self.rate_type, self.method_period
)
return self.compute_rate(self.rate, self.rate_type, self.method_period)
@api.depends('journal_id', 'company_id')
@api.depends("journal_id", "company_id")
def _compute_currency(self):
for rec in self:
rec.currency_id = (
rec.journal_id.currency_id or rec.company_id.currency_id)
rec.currency_id = rec.journal_id.currency_id or rec.company_id.currency_id
@api.depends('is_leasing')
@api.depends("is_leasing")
def _compute_journal_type(self):
for record in self:
if record.is_leasing:
record.journal_type = 'purchase'
record.journal_type = "purchase"
else:
record.journal_type = 'general'
record.journal_type = "general"
@api.onchange('is_leasing')
@api.onchange("is_leasing")
def _onchange_is_leasing(self):
self.journal_id = self.env['account.journal'].search([
('company_id', '=', self.company_id.id),
('type', '=', 'purchase' if self.is_leasing else 'general')
], limit=1)
self.journal_id = self.env["account.journal"].search(
[
("company_id", "=", self.company_id.id),
("type", "=", "purchase" if self.is_leasing else "general"),
],
limit=1,
)
self.residual_amount = 0.0
@api.onchange('company_id')
@api.onchange("company_id")
def _onchange_company(self):
self._onchange_is_leasing()
self.interest_expenses_account_id = self.short_term_loan_account_id = \
self.long_term_loan_account_id = False
self.interest_expenses_account_id = (
self.short_term_loan_account_id
) = self.long_term_loan_account_id = False
def get_default_name(self, vals):
return self.env['ir.sequence'].next_by_code('account.loan') or '/'
return self.env["ir.sequence"].next_by_code("account.loan") or "/"
@api.model
def create(self, vals):
if vals.get('name', '/') == '/':
vals['name'] = self.get_default_name(vals)
if vals.get("name", "/") == "/":
vals["name"] = self.get_default_name(vals)
return super().create(vals)
@api.multi
@ -366,16 +347,16 @@ class AccountLoan(models.Model):
if not self.start_date:
self.start_date = fields.Date.today()
self.compute_draft_lines()
self.write({'state': 'posted'})
self.write({"state": "posted"})
@api.multi
def close(self):
self.write({'state': 'closed'})
self.write({"state": "closed"})
@api.multi
def compute_lines(self):
self.ensure_one()
if self.state == 'draft':
if self.state == "draft":
return self.compute_draft_lines()
return self.compute_posted_lines()
@ -384,7 +365,7 @@ class AccountLoan(models.Model):
Recompute the amounts of not finished lines. Useful if rate is changed
"""
amount = self.loan_amount
for line in self.line_ids.sorted('sequence'):
for line in self.line_ids.sorted("sequence"):
if line.move_ids:
amount = line.final_pending_principal_amount
else:
@ -403,25 +384,27 @@ class AccountLoan(models.Model):
amount = 0
if not lines:
return
final_sequence = min(lines.mapped('sequence'))
for line in lines.sorted('sequence', reverse=True):
final_sequence = min(lines.mapped("sequence"))
for line in lines.sorted("sequence", reverse=True):
date = line.date + relativedelta(months=12)
if self.state == 'draft' or line.sequence != final_sequence:
if self.state == "draft" or line.sequence != final_sequence:
line.long_term_pending_principal_amount = sum(
self.line_ids.filtered(
lambda r: r.date >= date
).mapped('principal_amount'))
self.line_ids.filtered(lambda r: r.date >= date).mapped(
"principal_amount"
)
)
line.long_term_principal_amount = (
line.long_term_pending_principal_amount - amount)
line.long_term_pending_principal_amount - amount
)
amount = line.long_term_pending_principal_amount
def new_line_vals(self, sequence, date, amount):
return {
'loan_id': self.id,
'sequence': sequence,
'date': date,
'pending_principal_amount': amount,
'rate': self.rate_period,
"loan_id": self.id,
"sequence": sequence,
"date": date,
"pending_principal_amount": amount,
"rate": self.rate_period,
}
@api.multi
@ -439,7 +422,7 @@ class AccountLoan(models.Model):
if not self.payment_on_first_period:
date += delta
for i in range(1, self.periods + 1):
line = self.env['account.loan.line'].create(
line = self.env["account.loan.line"].create(
self.new_line_vals(i, date, amount)
)
line.check_amount()
@ -451,20 +434,17 @@ class AccountLoan(models.Model):
@api.multi
def view_account_moves(self):
self.ensure_one()
action = self.env.ref('account.action_move_line_form')
action = self.env.ref("account.action_move_line_form")
result = action.read()[0]
result['domain'] = [('loan_id', '=', self.id)]
result["domain"] = [("loan_id", "=", self.id)]
return result
@api.multi
def view_account_invoices(self):
self.ensure_one()
action = self.env.ref('account.action_invoice_tree2')
action = self.env.ref("account.action_invoice_tree2")
result = action.read()[0]
result['domain'] = [
('loan_id', '=', self.id),
('type', '=', 'in_invoice')
]
result["domain"] = [("loan_id", "=", self.id), ("type", "=", "in_invoice")]
return result
@api.model
@ -475,10 +455,9 @@ class AccountLoan(models.Model):
:return:
"""
res = []
for record in self.search([
('state', '=', 'posted'),
('is_leasing', '=', False)
]):
for record in self.search(
[("state", "=", "posted"), ("is_leasing", "=", False)]
):
lines = record.line_ids.filtered(
lambda r: r.date <= date and not r.move_ids
)
@ -488,10 +467,9 @@ class AccountLoan(models.Model):
@api.model
def generate_leasing_entries(self, date):
res = []
for record in self.search([
('state', '=', 'posted'),
('is_leasing', '=', True)
]):
for record in self.search(
[("state", "=", "posted"), ("is_leasing", "=", True)]
):
res += record.line_ids.filtered(
lambda r: r.date <= date and not r.invoice_ids
).generate_invoice()

View File

@ -1,10 +1,11 @@
# Copyright 2018 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models, _
from odoo.exceptions import UserError
import logging
from odoo import _, api, fields, models
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
try:
import numpy
@ -13,128 +14,112 @@ except (ImportError, IOError) as err:
class AccountLoanLine(models.Model):
_name = 'account.loan.line'
_description = 'Annuity'
_order = 'sequence asc'
_name = "account.loan.line"
_description = "Annuity"
_order = "sequence asc"
name = fields.Char(compute='_compute_name')
name = fields.Char(compute="_compute_name")
loan_id = fields.Many2one(
'account.loan',
required=True,
"account.loan", required=True, readonly=True, ondelete="cascade",
)
is_leasing = fields.Boolean(related="loan_id.is_leasing", readonly=True,)
loan_type = fields.Selection(
[
("fixed-annuity", "Fixed Annuity"),
("fixed-principal", "Fixed Principal"),
("interest", "Only interest"),
],
related="loan_id.loan_type",
readonly=True,
ondelete='cascade',
)
is_leasing = fields.Boolean(related='loan_id.is_leasing', readonly=True, )
loan_type = fields.Selection([
('fixed-annuity', 'Fixed Annuity'),
('fixed-principal', 'Fixed Principal'),
('interest', 'Only interest'),
], related='loan_id.loan_type', readonly=True,
loan_state = fields.Selection(
[
("draft", "Draft"),
("posted", "Posted"),
("cancelled", "Cancelled"),
("closed", "Closed"),
],
related="loan_id.state",
readonly=True,
store=True,
)
loan_state = fields.Selection([
('draft', 'Draft'),
('posted', 'Posted'),
('cancelled', 'Cancelled'),
('closed', 'Closed'),
], related='loan_id.state', readonly=True, store=True)
sequence = fields.Integer(required=True, readonly=True)
date = fields.Date(
required=True,
readonly=True,
help='Date when the payment will be accounted',
required=True, readonly=True, help="Date when the payment will be accounted",
)
long_term_loan_account_id = fields.Many2one(
'account.account',
readony=True,
related='loan_id.long_term_loan_account_id',
)
currency_id = fields.Many2one(
'res.currency',
related='loan_id.currency_id',
)
rate = fields.Float(
required=True,
readonly=True,
digits=(8, 6),
"account.account", readony=True, related="loan_id.long_term_loan_account_id",
)
currency_id = fields.Many2one("res.currency", related="loan_id.currency_id",)
rate = fields.Float(required=True, readonly=True, digits=(8, 6),)
pending_principal_amount = fields.Monetary(
currency_field='currency_id',
currency_field="currency_id",
readonly=True,
help='Pending amount of the loan before the payment',
help="Pending amount of the loan before the payment",
)
long_term_pending_principal_amount = fields.Monetary(
currency_field='currency_id',
currency_field="currency_id",
readonly=True,
help='Pending amount of the loan before the payment that will not be '
'payed in, at least, 12 months',
help="Pending amount of the loan before the payment that will not be "
"payed in, at least, 12 months",
)
payment_amount = fields.Monetary(
currency_field='currency_id',
currency_field="currency_id",
readonly=True,
help='Total amount that will be payed (Annuity)',
help="Total amount that will be payed (Annuity)",
)
interests_amount = fields.Monetary(
currency_field='currency_id',
currency_field="currency_id",
readonly=True,
help='Amount of the payment that will be assigned to interests',
help="Amount of the payment that will be assigned to interests",
)
principal_amount = fields.Monetary(
currency_field='currency_id',
compute='_compute_amounts',
help='Amount of the payment that will reduce the pending loan amount',
currency_field="currency_id",
compute="_compute_amounts",
help="Amount of the payment that will reduce the pending loan amount",
)
long_term_principal_amount = fields.Monetary(
currency_field='currency_id',
currency_field="currency_id",
readonly=True,
help='Amount that will reduce the pending loan amount on long term',
help="Amount that will reduce the pending loan amount on long term",
)
final_pending_principal_amount = fields.Monetary(
currency_field='currency_id',
compute='_compute_amounts',
help='Pending amount of the loan after the payment',
)
move_ids = fields.One2many(
'account.move',
inverse_name='loan_line_id',
)
has_moves = fields.Boolean(
compute='_compute_has_moves'
)
invoice_ids = fields.One2many(
'account.invoice',
inverse_name='loan_line_id',
)
has_invoices = fields.Boolean(
compute='_compute_has_invoices'
currency_field="currency_id",
compute="_compute_amounts",
help="Pending amount of the loan after the payment",
)
move_ids = fields.One2many("account.move", inverse_name="loan_line_id",)
has_moves = fields.Boolean(compute="_compute_has_moves")
invoice_ids = fields.One2many("account.invoice", inverse_name="loan_line_id",)
has_invoices = fields.Boolean(compute="_compute_has_invoices")
_sql_constraints = [
('sequence_loan',
'unique(loan_id, sequence)',
'Sequence must be unique in a loan')
(
"sequence_loan",
"unique(loan_id, sequence)",
"Sequence must be unique in a loan",
)
]
@api.depends('move_ids')
@api.depends("move_ids")
def _compute_has_moves(self):
for record in self:
record.has_moves = bool(record.move_ids)
@api.depends('invoice_ids')
@api.depends("invoice_ids")
def _compute_has_invoices(self):
for record in self:
record.has_invoices = bool(record.invoice_ids)
@api.depends('loan_id.name', 'sequence')
@api.depends("loan_id.name", "sequence")
def _compute_name(self):
for record in self:
record.name = '%s-%d' % (record.loan_id.name, record.sequence)
record.name = "%s-%d" % (record.loan_id.name, record.sequence)
@api.depends('payment_amount', 'interests_amount',
'pending_principal_amount')
@api.depends("payment_amount", "interests_amount", "pending_principal_amount")
def _compute_amounts(self):
for rec in self:
rec.final_pending_principal_amount = (
rec.pending_principal_amount - rec.payment_amount +
rec.interests_amount
rec.pending_principal_amount - rec.payment_amount + rec.interests_amount
)
rec.principal_amount = rec.payment_amount - rec.interests_amount
@ -144,76 +129,76 @@ class AccountLoanLine(models.Model):
:return: Amount to be payed on the annuity
"""
if self.sequence == self.loan_id.periods:
return (self.pending_principal_amount + self.interests_amount -
self.loan_id.residual_amount)
if self.loan_type == 'fixed-principal' and self.loan_id.round_on_end:
return self.loan_id.fixed_amount + self.interests_amount
if self.loan_type == 'fixed-principal':
return (
self.pending_principal_amount -
self.loan_id.residual_amount
) / (
self.pending_principal_amount
+ self.interests_amount
- self.loan_id.residual_amount
)
if self.loan_type == "fixed-principal" and self.loan_id.round_on_end:
return self.loan_id.fixed_amount + self.interests_amount
if self.loan_type == "fixed-principal":
return (self.pending_principal_amount - self.loan_id.residual_amount) / (
self.loan_id.periods - self.sequence + 1
) + self.interests_amount
if self.loan_type == 'interest':
if self.loan_type == "interest":
return self.interests_amount
if self.loan_type == 'fixed-annuity' and self.loan_id.round_on_end:
if self.loan_type == "fixed-annuity" and self.loan_id.round_on_end:
return self.loan_id.fixed_amount
if self.loan_type == 'fixed-annuity':
return self.currency_id.round(- numpy.pmt(
self.loan_id.loan_rate() / 100,
self.loan_id.periods - self.sequence + 1,
self.pending_principal_amount,
-self.loan_id.residual_amount
))
if (
self.loan_type == 'fixed-annuity-begin' and
self.loan_id.round_on_end
):
if self.loan_type == "fixed-annuity":
return self.currency_id.round(
-numpy.pmt(
self.loan_id.loan_rate() / 100,
self.loan_id.periods - self.sequence + 1,
self.pending_principal_amount,
-self.loan_id.residual_amount,
)
)
if self.loan_type == "fixed-annuity-begin" and self.loan_id.round_on_end:
return self.loan_id.fixed_amount
if self.loan_type == 'fixed-annuity-begin':
return self.currency_id.round(- numpy.pmt(
self.loan_id.loan_rate() / 100,
self.loan_id.periods - self.sequence + 1,
self.pending_principal_amount,
-self.loan_id.residual_amount,
when='begin'
))
if self.loan_type == "fixed-annuity-begin":
return self.currency_id.round(
-numpy.pmt(
self.loan_id.loan_rate() / 100,
self.loan_id.periods - self.sequence + 1,
self.pending_principal_amount,
-self.loan_id.residual_amount,
when="begin",
)
)
def check_amount(self):
"""Recompute amounts if the annuity has not been processed"""
if self.move_ids or self.invoice_ids:
raise UserError(_(
'Amount cannot be recomputed if moves or invoices exists '
'already'
))
raise UserError(
_("Amount cannot be recomputed if moves or invoices exists " "already")
)
if (
self.sequence == self.loan_id.periods and
self.loan_id.round_on_end and
self.loan_type in ['fixed-annuity', 'fixed-annuity-begin']
self.sequence == self.loan_id.periods
and self.loan_id.round_on_end
and self.loan_type in ["fixed-annuity", "fixed-annuity-begin"]
):
self.interests_amount = self.currency_id.round(
self.loan_id.fixed_amount - self.pending_principal_amount +
self.loan_id.residual_amount
self.loan_id.fixed_amount
- self.pending_principal_amount
+ self.loan_id.residual_amount
)
self.payment_amount = self.currency_id.round(self.compute_amount())
elif not self.loan_id.round_on_end:
self.interests_amount = self.currency_id.round(
self.compute_interest())
self.interests_amount = self.currency_id.round(self.compute_interest())
self.payment_amount = self.currency_id.round(self.compute_amount())
else:
self.interests_amount = self.compute_interest()
self.payment_amount = self.compute_amount()
def compute_interest(self):
if self.loan_type == 'fixed-annuity-begin':
if self.loan_type == "fixed-annuity-begin":
return -numpy.ipmt(
self.loan_id.loan_rate() / 100,
2,
self.loan_id.periods - self.sequence + 1,
self.pending_principal_amount,
-self.loan_id.residual_amount,
when='begin'
when="begin",
)
return self.pending_principal_amount * self.loan_id.loan_rate() / 100
@ -224,105 +209,118 @@ class AccountLoanLine(models.Model):
:return:
"""
self.ensure_one()
interests_moves = self.move_ids.mapped('line_ids').filtered(
interests_moves = self.move_ids.mapped("line_ids").filtered(
lambda r: r.account_id == self.loan_id.interest_expenses_account_id
)
short_term_moves = self.move_ids.mapped('line_ids').filtered(
short_term_moves = self.move_ids.mapped("line_ids").filtered(
lambda r: r.account_id == self.loan_id.short_term_loan_account_id
)
long_term_moves = self.move_ids.mapped('line_ids').filtered(
long_term_moves = self.move_ids.mapped("line_ids").filtered(
lambda r: r.account_id == self.loan_id.long_term_loan_account_id
)
self.interests_amount = (
sum(interests_moves.mapped('debit')) -
sum(interests_moves.mapped('credit'))
self.interests_amount = sum(interests_moves.mapped("debit")) - sum(
interests_moves.mapped("credit")
)
self.long_term_principal_amount = (
sum(long_term_moves.mapped('debit')) -
sum(long_term_moves.mapped('credit'))
self.long_term_principal_amount = sum(long_term_moves.mapped("debit")) - sum(
long_term_moves.mapped("credit")
)
self.payment_amount = (
sum(short_term_moves.mapped('debit')) -
sum(short_term_moves.mapped('credit')) +
self.long_term_principal_amount +
self.interests_amount
sum(short_term_moves.mapped("debit"))
- sum(short_term_moves.mapped("credit"))
+ self.long_term_principal_amount
+ self.interests_amount
)
def move_vals(self):
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': [(0, 0, vals) for vals in self.move_line_vals()]
"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": [(0, 0, vals) for vals in self.move_line_vals()],
}
def move_line_vals(self):
vals = []
partner = self.loan_id.partner_id.with_context(
force_company=self.loan_id.company_id.id)
vals.append({
'account_id': 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,
})
vals.append({
'account_id': self.loan_id.short_term_loan_account_id.id,
'credit': 0,
'debit': self.payment_amount - self.interests_amount,
})
force_company=self.loan_id.company_id.id
)
vals.append(
{
"account_id": 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,
}
)
vals.append(
{
"account_id": self.loan_id.short_term_loan_account_id.id,
"credit": 0,
"debit": self.payment_amount - self.interests_amount,
}
)
if self.long_term_loan_account_id and self.long_term_principal_amount:
vals.append({
'account_id': self.loan_id.short_term_loan_account_id.id,
'credit': self.long_term_principal_amount,
'debit': 0,
})
vals.append({
'account_id': self.long_term_loan_account_id.id,
'credit': 0,
'debit': self.long_term_principal_amount,
})
vals.append(
{
"account_id": self.loan_id.short_term_loan_account_id.id,
"credit": self.long_term_principal_amount,
"debit": 0,
}
)
vals.append(
{
"account_id": self.long_term_loan_account_id.id,
"credit": 0,
"debit": self.long_term_principal_amount,
}
)
return vals
def invoice_vals(self):
partner = self.loan_id.partner_id.with_context(
force_company=self.loan_id.company_id.id)
force_company=self.loan_id.company_id.id
)
return {
'loan_line_id': self.id,
'loan_id': self.loan_id.id,
'type': 'in_invoice',
'partner_id': self.loan_id.partner_id.id,
'date_invoice': self.date,
'account_id': partner.property_account_payable_id.id,
'journal_id': self.loan_id.journal_id.id,
'company_id': self.loan_id.company_id.id,
'invoice_line_ids': [(0, 0, vals) for vals in
self.invoice_line_vals()]
"loan_line_id": self.id,
"loan_id": self.loan_id.id,
"type": "in_invoice",
"partner_id": self.loan_id.partner_id.id,
"date_invoice": self.date,
"account_id": partner.property_account_payable_id.id,
"journal_id": self.loan_id.journal_id.id,
"company_id": self.loan_id.company_id.id,
"invoice_line_ids": [(0, 0, vals) for vals in self.invoice_line_vals()],
}
def invoice_line_vals(self):
vals = list()
vals.append({
'product_id': self.loan_id.product_id.id,
'name': self.loan_id.product_id.name,
'quantity': 1,
'price_unit': self.principal_amount,
'account_id': self.loan_id.short_term_loan_account_id.id,
})
vals.append({
'product_id': self.loan_id.interests_product_id.id,
'name': self.loan_id.interests_product_id.name,
'quantity': 1,
'price_unit': self.interests_amount,
'account_id': self.loan_id.interest_expenses_account_id.id,
})
vals.append(
{
"product_id": self.loan_id.product_id.id,
"name": self.loan_id.product_id.name,
"quantity": 1,
"price_unit": self.principal_amount,
"account_id": self.loan_id.short_term_loan_account_id.id,
}
)
vals.append(
{
"product_id": self.loan_id.interests_product_id.id,
"name": self.loan_id.interests_product_id.name,
"quantity": 1,
"price_unit": self.interests_amount,
"account_id": self.loan_id.interest_expenses_account_id.id,
}
)
return vals
@api.multi
@ -335,10 +333,10 @@ class AccountLoanLine(models.Model):
for record in self:
if not record.move_ids:
if record.loan_id.line_ids.filtered(
lambda r: r.date < record.date and not r.move_ids
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())
raise UserError(_("Some moves must be created first"))
move = self.env["account.move"].create(record.move_vals())
move.post()
res.append(move.id)
return res
@ -353,11 +351,10 @@ class AccountLoanLine(models.Model):
for record in self:
if not record.invoice_ids:
if record.loan_id.line_ids.filtered(
lambda r: r.date < record.date and not r.invoice_ids
lambda r: r.date < record.date and not r.invoice_ids
):
raise UserError(_('Some invoices must be created first'))
invoice = self.env['account.invoice'].create(
record.invoice_vals())
raise UserError(_("Some invoices must be created first"))
invoice = self.env["account.invoice"].create(record.invoice_vals())
res.append(invoice.id)
for line in invoice.invoice_line_ids:
line._set_taxes()
@ -387,34 +384,31 @@ class AccountLoanLine(models.Model):
@api.multi
def view_account_moves(self):
self.ensure_one()
action = self.env.ref('account.action_move_line_form')
action = self.env.ref("account.action_move_line_form")
result = action.read()[0]
result['context'] = {
'default_loan_line_id': self.id,
'default_loan_id': self.loan_id.id
result["context"] = {
"default_loan_line_id": self.id,
"default_loan_id": self.loan_id.id,
}
result['domain'] = [('loan_line_id', '=', self.id)]
result["domain"] = [("loan_line_id", "=", self.id)]
if len(self.move_ids) == 1:
res = self.env.ref('account.move.form', False)
result['views'] = [(res and res.id or False, 'form')]
result['res_id'] = self.move_ids.id
res = self.env.ref("account.move.form", False)
result["views"] = [(res and res.id or False, "form")]
result["res_id"] = self.move_ids.id
return result
@api.multi
def view_account_invoices(self):
self.ensure_one()
action = self.env.ref('account.action_invoice_tree2')
action = self.env.ref("account.action_invoice_tree2")
result = action.read()[0]
result['context'] = {
'default_loan_line_id': self.id,
'default_loan_id': self.loan_id.id
result["context"] = {
"default_loan_line_id": self.id,
"default_loan_id": self.loan_id.id,
}
result['domain'] = [
('loan_line_id', '=', self.id),
('type', '=', 'in_invoice')
]
result["domain"] = [("loan_line_id", "=", self.id), ("type", "=", "in_invoice")]
if len(self.invoice_ids) == 1:
res = self.env.ref('account.invoice.supplier.form', False)
result['views'] = [(res and res.id or False, 'form')]
result['res_id'] = self.invoice_ids.id
res = self.env.ref("account.invoice.supplier.form", False)
result["views"] = [(res and res.id or False, "form")]
result["res_id"] = self.invoice_ids.id
return result

View File

@ -5,27 +5,20 @@ from odoo import api, fields, models
class AccountMove(models.Model):
_inherit = 'account.move'
_inherit = "account.move"
loan_line_id = fields.Many2one(
'account.loan.line',
readonly=True,
ondelete='restrict',
"account.loan.line", readonly=True, ondelete="restrict",
)
loan_id = fields.Many2one(
'account.loan',
readonly=True,
store=True,
ondelete='restrict',
"account.loan", readonly=True, store=True, ondelete="restrict",
)
@api.multi
def post(self, invoice=False):
res = super().post(invoice=invoice)
for record in self:
loan_line_id = record.loan_line_id or (
invoice and invoice.loan_line_id
)
loan_line_id = record.loan_line_id or (invoice and invoice.loan_line_id)
if loan_line_id:
if not record.loan_line_id:
record.loan_line_id = loan_line_id

View File

@ -1,12 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="account_loan_multi_company_rule" model="ir.rule">
<field name="name">Account loan multi-company</field>
<field ref="model_account_loan" name="model_id"/>
<field eval="True" name="global"/>
<field ref="model_account_loan" name="model_id" />
<field eval="True" name="global" />
<field name="domain_force">
['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
</field>
</record>
</odoo>

View File

@ -1,13 +1,14 @@
# Copyright 2018 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import logging
from dateutil.relativedelta import relativedelta
from odoo import fields
from odoo.exceptions import UserError
from odoo.tests import TransactionCase
from dateutil.relativedelta import relativedelta
import logging
_logger = logging.getLogger(__name__)
try:
import numpy
@ -18,67 +19,60 @@ except (ImportError, IOError) as err:
class TestLoan(TransactionCase):
def setUp(self):
super().setUp()
self.company = self.browse_ref('base.main_company')
self.company_02 = self.env['res.company'].create({
'name': 'Auxiliar company'
})
self.journal = self.env['account.journal'].create({
'company_id': self.company.id,
'type': 'purchase',
'name': 'Debts',
'code': 'DBT',
})
self.company = self.browse_ref("base.main_company")
self.company_02 = self.env["res.company"].create({"name": "Auxiliar company"})
self.journal = self.env["account.journal"].create(
{
"company_id": self.company.id,
"type": "purchase",
"name": "Debts",
"code": "DBT",
}
)
self.loan_account = self.create_account(
'DEP',
'depreciation',
self.browse_ref('account.data_account_type_current_liabilities').id
"DEP",
"depreciation",
self.browse_ref("account.data_account_type_current_liabilities").id,
)
self.payable_account = self.create_account(
'PAY',
'payable',
self.browse_ref('account.data_account_type_payable').id
"PAY", "payable", self.browse_ref("account.data_account_type_payable").id
)
self.asset_account = self.create_account(
'ASSET',
'asset',
self.browse_ref('account.data_account_type_payable').id
"ASSET", "asset", self.browse_ref("account.data_account_type_payable").id
)
self.interests_account = self.create_account(
'FEE',
'Fees',
self.browse_ref('account.data_account_type_expenses').id)
"FEE", "Fees", self.browse_ref("account.data_account_type_expenses").id
)
self.lt_loan_account = self.create_account(
'LTD',
'Long term depreciation',
self.browse_ref(
'account.data_account_type_non_current_liabilities').id)
self.partner = self.env['res.partner'].create({
'name': 'Bank'
})
self.product = self.env['product.product'].create({
'name': 'Payment',
'type': 'service'
})
self.interests_product = self.env['product.product'].create({
'name': 'Bank fee',
'type': 'service'
})
"LTD",
"Long term depreciation",
self.browse_ref("account.data_account_type_non_current_liabilities").id,
)
self.partner = self.env["res.partner"].create({"name": "Bank"})
self.product = self.env["product.product"].create(
{"name": "Payment", "type": "service"}
)
self.interests_product = self.env["product.product"].create(
{"name": "Bank fee", "type": "service"}
)
def test_onchange(self):
loan = self.env['account.loan'].new({
'name': 'LOAN',
'company_id': self.company.id,
'journal_id': self.journal.id,
'loan_type': 'fixed-annuity',
'loan_amount': 100,
'rate': 1,
'periods': 2,
'short_term_loan_account_id': self.loan_account.id,
'interest_expenses_account_id': self.interests_account.id,
'product_id': self.product.id,
'interests_product_id': self.interests_product.id,
'partner_id': self.partner.id,
})
loan = self.env["account.loan"].new(
{
"name": "LOAN",
"company_id": self.company.id,
"journal_id": self.journal.id,
"loan_type": "fixed-annuity",
"loan_amount": 100,
"rate": 1,
"periods": 2,
"short_term_loan_account_id": self.loan_account.id,
"interest_expenses_account_id": self.interests_account.id,
"product_id": self.product.id,
"interests_product_id": self.interests_product.id,
"partner_id": self.partner.id,
}
)
journal = loan.journal_id.id
loan.is_leasing = True
loan._onchange_is_leasing()
@ -88,20 +82,18 @@ class TestLoan(TransactionCase):
self.assertFalse(loan.interest_expenses_account_id)
def test_round_on_end(self):
loan = self.create_loan('fixed-annuity', 500000, 1, 60)
loan = self.create_loan("fixed-annuity", 500000, 1, 60)
loan.round_on_end = True
loan.compute_lines()
line_1 = loan.line_ids.filtered(lambda r: r.sequence == 1)
for line in loan.line_ids:
self.assertAlmostEqual(
line_1.payment_amount, line.payment_amount, 2)
loan.loan_type = 'fixed-principal'
self.assertAlmostEqual(line_1.payment_amount, line.payment_amount, 2)
loan.loan_type = "fixed-principal"
loan.compute_lines()
line_1 = loan.line_ids.filtered(lambda r: r.sequence == 1)
line_end = loan.line_ids.filtered(lambda r: r.sequence == 60)
self.assertNotAlmostEqual(
line_1.payment_amount, line_end.payment_amount, 2)
loan.loan_type = 'interest'
self.assertNotAlmostEqual(line_1.payment_amount, line_end.payment_amount, 2)
loan.loan_type = "interest"
loan.compute_lines()
line_1 = loan.line_ids.filtered(lambda r: r.sequence == 1)
line_end = loan.line_ids.filtered(lambda r: r.sequence == 60)
@ -111,12 +103,13 @@ class TestLoan(TransactionCase):
def test_pay_amount_validation(self):
amount = 10000
periods = 24
loan = self.create_loan('fixed-annuity', amount, 1, periods)
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.pmt(1 / 100 / 12, 24, 10000), line.payment_amount, 2)
-numpy.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()
@ -128,52 +121,45 @@ class TestLoan(TransactionCase):
self.assertTrue(line)
self.assertFalse(line.move_ids)
self.assertFalse(line.invoice_ids)
wzd = self.env['account.loan.generate.wizard'].create({})
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.assertIn(line.move_ids.id, action["domain"][0][2])
line.move_ids.post()
with self.assertRaises(UserError):
self.env['account.loan.pay.amount'].create({
'loan_id': loan.id,
'amount': (amount - amount / periods) / 2,
'fees': 100,
'date': line.date + relativedelta(months=-1)
}).run()
self.env["account.loan.pay.amount"].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"].create(
{"loan_id": loan.id, "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"].create(
{"loan_id": loan.id, "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"].create(
{"loan_id": loan.id, "amount": -100, "fees": 100, "date": line.date,}
).run()
def test_fixed_annuity_begin_loan(self):
amount = 10000
periods = 24
loan = self.create_loan('fixed-annuity-begin', amount, 1, periods)
loan = self.create_loan("fixed-annuity-begin", 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.pmt(1 / 100 / 12, 24, 10000, when='begin'),
line.payment_amount, 2)
-numpy.pmt(1 / 100 / 12, 24, 10000, when="begin"), 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()
@ -185,24 +171,28 @@ class TestLoan(TransactionCase):
self.assertTrue(line)
self.assertFalse(line.move_ids)
self.assertFalse(line.invoice_ids)
wzd = self.env['account.loan.generate.wizard'].create({})
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.assertIn(line.move_ids.id, action["domain"][0][2])
line.move_ids.post()
loan.rate = 2
loan.compute_lines()
line = loan.line_ids.filtered(lambda r: r.sequence == 1)
self.assertAlmostEqual(
- numpy.pmt(1 / 100 / 12, periods, amount, when='begin'),
line.payment_amount, 2)
-numpy.pmt(1 / 100 / 12, periods, amount, when="begin"),
line.payment_amount,
2,
)
line = loan.line_ids.filtered(lambda r: r.sequence == 2)
self.assertAlmostEqual(
- numpy.pmt(2 / 100 / 12, periods - 1,
line.pending_principal_amount, when='begin'),
line.payment_amount, 2
-numpy.pmt(
2 / 100 / 12, periods - 1, line.pending_principal_amount, when="begin"
),
line.payment_amount,
2,
)
line = loan.line_ids.filtered(lambda r: r.sequence == 3)
with self.assertRaises(UserError):
@ -211,12 +201,13 @@ class TestLoan(TransactionCase):
def test_fixed_annuity_loan(self):
amount = 10000
periods = 24
loan = self.create_loan('fixed-annuity', amount, 1, periods)
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.pmt(1 / 100 / 12, 24, 10000), line.payment_amount, 2)
-numpy.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()
@ -228,23 +219,24 @@ class TestLoan(TransactionCase):
self.assertTrue(line)
self.assertFalse(line.move_ids)
self.assertFalse(line.invoice_ids)
wzd = self.env['account.loan.generate.wizard'].create({})
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.assertIn(line.move_ids.id, action["domain"][0][2])
line.move_ids.post()
loan.rate = 2
loan.compute_lines()
line = loan.line_ids.filtered(lambda r: r.sequence == 1)
self.assertAlmostEqual(
- numpy.pmt(1 / 100 / 12, periods, amount), line.payment_amount, 2)
-numpy.pmt(1 / 100 / 12, periods, amount), line.payment_amount, 2
)
line = loan.line_ids.filtered(lambda r: r.sequence == 2)
self.assertAlmostEqual(
- numpy.pmt(2 / 100 / 12, periods - 1,
line.pending_principal_amount),
line.payment_amount, 2
-numpy.pmt(2 / 100 / 12, periods - 1, line.pending_principal_amount),
line.payment_amount,
2,
)
line = loan.line_ids.filtered(lambda r: r.sequence == 3)
with self.assertRaises(UserError):
@ -253,14 +245,14 @@ class TestLoan(TransactionCase):
def test_fixed_principal_loan(self):
amount = 24000
periods = 24
loan = self.create_loan('fixed-principal', amount, 1, periods)
loan = self.create_loan("fixed-principal", amount, 1, periods)
self.partner.property_account_payable_id = self.payable_account
self.assertEqual(loan.journal_type, 'general')
self.assertEqual(loan.journal_type, "general")
loan.is_leasing = True
loan.post_invoice = False
self.assertEqual(loan.journal_type, 'purchase')
self.assertEqual(loan.journal_type, "purchase")
loan.long_term_loan_account_id = self.lt_loan_account
loan.rate_type = 'real'
loan.rate_type = "real"
loan.compute_lines()
self.assertTrue(loan.line_ids)
self.assertEqual(len(loan.line_ids), periods)
@ -272,60 +264,69 @@ class TestLoan(TransactionCase):
self.assertTrue(line)
self.assertFalse(line.has_invoices)
self.assertFalse(line.has_moves)
action = self.env['account.loan.generate.wizard'].create({
'date': fields.date.today(),
'loan_type': 'leasing',
}).run()
action = (
self.env["account.loan.generate.wizard"]
.create({"date": fields.date.today(), "loan_type": "leasing",})
.run()
)
self.assertTrue(line.has_invoices)
self.assertFalse(line.has_moves)
self.assertIn(line.invoice_ids.id, action['domain'][0][2])
self.assertIn(line.invoice_ids.id, action["domain"][0][2])
with self.assertRaises(UserError):
self.env['account.loan.pay.amount'].create({
'loan_id': loan.id,
'amount': (amount - amount / periods) / 2,
'fees': 100,
'date': loan.line_ids.filtered(
lambda r: r.sequence == 2).date
}).run()
self.env["account.loan.pay.amount"].create(
{
"loan_id": loan.id,
"amount": (amount - amount / periods) / 2,
"fees": 100,
"date": loan.line_ids.filtered(lambda r: r.sequence == 2).date,
}
).run()
with self.assertRaises(UserError):
self.env['account.loan.pay.amount'].create({
'loan_id': loan.id,
'amount': (amount - amount / periods) / 2,
'fees': 100,
'date': loan.line_ids.filtered(
lambda r: r.sequence == 1
).date + relativedelta(months=-1)
}).run()
self.env["account.loan.pay.amount"].create(
{
"loan_id": loan.id,
"amount": (amount - amount / periods) / 2,
"fees": 100,
"date": loan.line_ids.filtered(lambda r: r.sequence == 1).date
+ relativedelta(months=-1),
}
).run()
line.invoice_ids.action_invoice_open()
self.assertTrue(line.has_moves)
self.assertIn(
line.move_ids.id,
self.env['account.move'].search(
loan.view_account_moves()['domain']).ids
self.env["account.move"].search(loan.view_account_moves()["domain"]).ids,
)
self.assertEqual(
line.invoice_ids.id,
self.env['account.invoice'].search(
loan.view_account_invoices()['domain']).id
self.env["account.invoice"]
.search(loan.view_account_invoices()["domain"])
.id,
)
with self.assertRaises(UserError):
self.env['account.loan.pay.amount'].create({
'loan_id': loan.id,
'amount': (amount - amount / periods) / 2,
'fees': 100,
'date': loan.line_ids.filtered(
lambda r: r.sequence == periods).date
}).run()
self.env['account.loan.pay.amount'].create({
'loan_id': loan.id,
'amount': (amount - amount / periods) / 2,
'date': line.date,
'fees': 100,
}).run()
self.env["account.loan.pay.amount"].create(
{
"loan_id": loan.id,
"amount": (amount - amount / periods) / 2,
"fees": 100,
"date": loan.line_ids.filtered(
lambda r: r.sequence == periods
).date,
}
).run()
self.env["account.loan.pay.amount"].create(
{
"loan_id": loan.id,
"amount": (amount - amount / periods) / 2,
"date": line.date,
"fees": 100,
}
).run()
line = loan.line_ids.filtered(lambda r: r.sequence == 2)
self.assertEqual(loan.periods, periods + 1)
self.assertAlmostEqual(
line.principal_amount, (amount - amount / periods) / 2, 2)
line.principal_amount, (amount - amount / periods) / 2, 2
)
line = loan.line_ids.filtered(lambda r: r.sequence == 3)
self.assertEqual(amount / periods / 2, line.principal_amount)
line = loan.line_ids.filtered(lambda r: r.sequence == 4)
@ -335,13 +336,13 @@ class TestLoan(TransactionCase):
def test_fixed_principal_loan_auto_post(self):
amount = 24000
periods = 24
loan = self.create_loan('fixed-principal', amount, 1, periods)
loan = self.create_loan("fixed-principal", amount, 1, periods)
self.partner.property_account_payable_id = self.payable_account
self.assertEqual(loan.journal_type, 'general')
self.assertEqual(loan.journal_type, "general")
loan.is_leasing = True
self.assertEqual(loan.journal_type, 'purchase')
self.assertEqual(loan.journal_type, "purchase")
loan.long_term_loan_account_id = self.lt_loan_account
loan.rate_type = 'real'
loan.rate_type = "real"
loan.compute_lines()
self.assertTrue(loan.line_ids)
self.assertEqual(len(loan.line_ids), periods)
@ -353,38 +354,37 @@ class TestLoan(TransactionCase):
self.assertTrue(line)
self.assertFalse(line.has_invoices)
self.assertFalse(line.has_moves)
self.env['account.loan.generate.wizard'].create({
'date': fields.date.today(),
'loan_type': 'leasing',
}).run()
self.env["account.loan.generate.wizard"].create(
{"date": fields.date.today(), "loan_type": "leasing",}
).run()
self.assertTrue(line.has_invoices)
self.assertTrue(line.has_moves)
def test_interests_on_end_loan(self):
amount = 10000
periods = 10
loan = self.create_loan('interest', amount, 1, periods)
loan = self.create_loan("interest", amount, 1, periods)
loan.payment_on_first_period = False
loan.start_date = fields.Date.today()
loan.rate_type = 'ear'
loan.rate_type = "ear"
loan.compute_lines()
self.assertTrue(loan.line_ids)
self.assertEqual(len(loan.line_ids), periods)
self.assertEqual(0, loan.line_ids[0].principal_amount)
self.assertEqual(amount, loan.line_ids.filtered(
lambda r: r.sequence == periods
).principal_amount)
self.assertEqual(
amount,
loan.line_ids.filtered(lambda r: r.sequence == periods).principal_amount,
)
self.post(loan)
self.assertEqual(loan.payment_amount, 0)
self.assertEqual(loan.interests_amount, 0)
self.assertEqual(loan.pending_principal_amount, amount)
self.assertFalse(loan.line_ids.filtered(
lambda r: r.date <= loan.start_date))
self.assertFalse(loan.line_ids.filtered(lambda r: r.date <= loan.start_date))
for line in loan.line_ids:
self.assertEqual(loan.state, 'posted')
self.assertEqual(loan.state, "posted")
line.view_process_values()
line.move_ids.post()
self.assertEqual(loan.state, 'closed')
self.assertEqual(loan.state, "closed")
self.assertEqual(loan.payment_amount - loan.interests_amount, amount)
self.assertEqual(loan.pending_principal_amount, 0)
@ -392,57 +392,60 @@ class TestLoan(TransactionCase):
def test_cancel_loan(self):
amount = 10000
periods = 10
loan = self.create_loan('fixed-annuity', amount, 1, periods)
loan = self.create_loan("fixed-annuity", amount, 1, periods)
self.post(loan)
line = loan.line_ids.filtered(lambda r: r.sequence == 1)
line.view_process_values()
line.move_ids.post()
pay = self.env['account.loan.pay.amount'].create({
'loan_id': loan.id,
'amount': 0,
'fees': 100,
'date': line.date
})
pay = self.env["account.loan.pay.amount"].create(
{"loan_id": loan.id, "amount": 0, "fees": 100, "date": line.date}
)
pay.cancel_loan = True
pay._onchange_cancel_loan()
self.assertEqual(pay.amount, line.final_pending_principal_amount)
pay.run()
self.assertEqual(loan.state, 'cancelled')
self.assertEqual(loan.state, "cancelled")
def post(self, loan):
self.assertFalse(loan.move_ids)
post = self.env['account.loan.post'].with_context(
default_loan_id=loan.id
).create({})
post = (
self.env["account.loan.post"]
.with_context(default_loan_id=loan.id)
.create({})
)
post.run()
self.assertTrue(loan.move_ids)
with self.assertRaises(UserError):
post.run()
def create_account(self, code, name, type_id):
return self.env['account.account'].create({
'company_id': self.company.id,
'name': name,
'code': code,
'user_type_id': type_id,
'reconcile': True,
})
return self.env["account.account"].create(
{
"company_id": self.company.id,
"name": name,
"code": code,
"user_type_id": type_id,
"reconcile": True,
}
)
def create_loan(self, type_loan, amount, rate, periods):
loan = self.env['account.loan'].create({
'journal_id': self.journal.id,
'rate_type': 'napr',
'loan_type': type_loan,
'loan_amount': amount,
'payment_on_first_period': True,
'rate': rate,
'periods': periods,
'leased_asset_account_id': self.asset_account.id,
'short_term_loan_account_id': self.loan_account.id,
'interest_expenses_account_id': self.interests_account.id,
'product_id': self.product.id,
'interests_product_id': self.interests_product.id,
'partner_id': self.partner.id,
})
loan = self.env["account.loan"].create(
{
"journal_id": self.journal.id,
"rate_type": "napr",
"loan_type": type_loan,
"loan_amount": amount,
"payment_on_first_period": True,
"rate": rate,
"periods": periods,
"leased_asset_account_id": self.asset_account.id,
"short_term_loan_account_id": self.loan_account.id,
"interest_expenses_account_id": self.interests_account.id,
"product_id": self.product.id,
"interests_product_id": self.interests_product.id,
"partner_id": self.partner.id,
}
)
loan.compute_lines()
return loan

View File

@ -1,202 +1,226 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2011 Alexis de Lattre <alexis.delattre@akretion.com>
Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="account_loan_tree" model="ir.ui.view">
<field name="name">account.loan.tree</field>
<field name="model">account.loan</field>
<field name="arch" type="xml">
<tree string="Loans">
<field name="name"/>
<field name="company_id"/>
<field name="is_leasing"/>
<field name="state"/>
<field name="name" />
<field name="company_id" />
<field name="is_leasing" />
<field name="state" />
</tree>
</field>
</record>
<record id="account_loan_form" model="ir.ui.view">
<field name="name">account.loan.form</field>
<field name="model">account.loan</field>
<field name="arch" type="xml">
<form string="Loan">
<header>
<button name="compute_lines" type="object"
string="Compute items"/>
<button name="%(account_loan_post_action)d"
states="draft" type="action"
string="Post"/>
<field name="state" widget="statusbar"/>
<button name="compute_lines" type="object" string="Compute items" />
<button
name="%(account_loan_post_action)d"
states="draft"
type="action"
string="Post"
/>
<field name="state" widget="statusbar" />
</header>
<sheet>
<div class="oe_button_box">
<button name="view_account_moves"
class="oe_stat_button"
icon="fa-bars"
attrs="{'invisible': [('state', '=', 'draft')]}"
type="object" string="Moves"/>
<button name="view_account_invoices"
class="oe_stat_button"
icon="fa-pencil-square-o"
attrs="{'invisible': ['|', ('state', '=', 'draft'), ('is_leasing', '=', False)]}"
type="object" string="Invoices"/>
<button name="%(account_loan_pay_amount_action)d"
class="oe_stat_button" icon="fa-usd"
attrs="{'invisible': [('state', '!=', 'posted')]}"
type="action" string="Pay amount"/>
<button
name="view_account_moves"
class="oe_stat_button"
icon="fa-bars"
attrs="{'invisible': [('state', '=', 'draft')]}"
type="object"
string="Moves"
/>
<button
name="view_account_invoices"
class="oe_stat_button"
icon="fa-pencil-square-o"
attrs="{'invisible': ['|', ('state', '=', 'draft'), ('is_leasing', '=', False)]}"
type="object"
string="Invoices"
/>
<button
name="%(account_loan_pay_amount_action)d"
class="oe_stat_button"
icon="fa-usd"
attrs="{'invisible': [('state', '!=', 'posted')]}"
type="action"
string="Pay amount"
/>
</div>
<h1>
<field name="name"/>
<field name="name" />
</h1>
<group>
<group>
<field name="company_id"
options="{'no_create': True}"/>
<field name="loan_type"/>
<field name="loan_amount"/>
<field name="company_id" options="{'no_create': True}" />
<field name="loan_type" />
<field name="loan_amount" />
</group>
<group>
<field name="rate_type"/>
<field name="rate"/>
<field name="rate_period"/>
<field name="rate_type" />
<field name="rate" />
<field name="rate_period" />
</group>
</group>
<group>
<group>
<field name="partner_id"/>
<field name="start_date"/>
<field name="periods"/>
<field name="method_period"/>
<field name="partner_id" />
<field name="start_date" />
<field name="periods" />
<field name="method_period" />
</group>
<group>
<field name="is_leasing"/>
<field name="round_on_end"/>
<field name="payment_on_first_period"/>
<field name="is_leasing" />
<field name="round_on_end" />
<field name="payment_on_first_period" />
</group>
</group>
<group attrs="{'invisible':[('state', '=', 'draft')]}">
<group>
<field name="pending_principal_amount"/>
<field name="payment_amount"/>
<field name="interests_amount"/>
<field name="pending_principal_amount" />
<field name="payment_amount" />
<field name="interests_amount" />
</group>
</group>
<notebook>
<page string="Items" id="items">
<field name="line_ids"/>
<field name="line_ids" />
</page>
<page string="Accounts" id="accounting">
<group>
<group>
<field name="journal_id"/>
<field name="short_term_loan_account_id"/>
<field name="journal_type" invisible="1"/>
<field name="journal_id" />
<field name="short_term_loan_account_id" />
<field name="journal_type" invisible="1" />
</group>
<group>
<field name="long_term_loan_account_id"/>
<field name="interest_expenses_account_id"/>
<field name="currency_id" invisible="1"/>
<field name="long_term_loan_account_id" />
<field name="interest_expenses_account_id" />
<field name="currency_id" invisible="1" />
</group>
</group>
</page>
<page string="Leasing" id="leasing"
attrs="{'invisible': [('is_leasing', '=', False)]}">
<page
string="Leasing"
id="leasing"
attrs="{'invisible': [('is_leasing', '=', False)]}"
>
<group>
<group>
<field name="leased_asset_account_id"
attrs="{'required': [('is_leasing', '=', True)]}"/>
<field name="residual_amount"/>
<field
name="leased_asset_account_id"
attrs="{'required': [('is_leasing', '=', True)]}"
/>
<field name="residual_amount" />
</group>
<group>
<field name="product_id"
attrs="{'required': [('is_leasing', '=', True)]}"/>
<field name="interests_product_id"
attrs="{'required': [('is_leasing', '=', True)]}"/>
<field name="post_invoice"/>
<field
name="product_id"
attrs="{'required': [('is_leasing', '=', True)]}"
/>
<field
name="interests_product_id"
attrs="{'required': [('is_leasing', '=', True)]}"
/>
<field name="post_invoice" />
</group>
</group>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids"
widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers" />
<field name="activity_ids" widget="mail_activity" />
<field name="message_ids" widget="mail_thread" />
</div>
</form>
</field>
</record>
<record id="account_loan_line_tree" model="ir.ui.view">
<field name="name">account.loan.line.tree</field>
<field name="model">account.loan.line</field>
<field name="arch" type="xml">
<tree string="Loan items" create="0">
<field name="sequence"/>
<field name="date"/>
<field name="rate"/>
<field name="pending_principal_amount"/>
<field name="payment_amount" sum="Total payments"/>
<field name="principal_amount"/>
<field name="interests_amount" sum="Total interests"/>
<field name="long_term_pending_principal_amount"
attrs="{'invisible': [('long_term_loan_account_id', '=', False)]}"/>
<field name="long_term_principal_amount"
attrs="{'invisible': [('long_term_loan_account_id', '=', False)]}"/>
<field name="long_term_loan_account_id" invisible="1"/>
<field name="loan_state" invisible="1"/>
<field name="is_leasing" invisible="1"/>
<field name="has_invoices" invisible="1"/>
<field name="has_moves" invisible="1"/>
<field name="currency_id" invisible="1"/>
<button name="view_account_values" string="Values"
type="object" icon="fa-eye"
attrs="{'invisible': [('has_moves', '=', False), ('has_invoices', '=', False)]}"/>
<button name="view_process_values" string="Process"
type="object" icon="fa-cogs"
attrs="{'invisible': ['|', '|', ('has_moves', '=', True), ('has_invoices', '=', True), ('loan_state', '!=', 'posted')]}"/>
<field name="sequence" />
<field name="date" />
<field name="rate" />
<field name="pending_principal_amount" />
<field name="payment_amount" sum="Total payments" />
<field name="principal_amount" />
<field name="interests_amount" sum="Total interests" />
<field
name="long_term_pending_principal_amount"
attrs="{'invisible': [('long_term_loan_account_id', '=', False)]}"
/>
<field
name="long_term_principal_amount"
attrs="{'invisible': [('long_term_loan_account_id', '=', False)]}"
/>
<field name="long_term_loan_account_id" invisible="1" />
<field name="loan_state" invisible="1" />
<field name="is_leasing" invisible="1" />
<field name="has_invoices" invisible="1" />
<field name="has_moves" invisible="1" />
<field name="currency_id" invisible="1" />
<button
name="view_account_values"
string="Values"
type="object"
icon="fa-eye"
attrs="{'invisible': [('has_moves', '=', False), ('has_invoices', '=', False)]}"
/>
<button
name="view_process_values"
string="Process"
type="object"
icon="fa-cogs"
attrs="{'invisible': ['|', '|', ('has_moves', '=', True), ('has_invoices', '=', True), ('loan_state', '!=', 'posted')]}"
/>
</tree>
</field>
</record>
<record id="account_loan_line_form" model="ir.ui.view">
<field name="name">account.loan.line.form</field>
<field name="model">account.loan.line</field>
<field name="arch" type="xml">
<form>
<group>
<field name="sequence"/>
<field name="rate"/>
<field name="date"/>
<field name="sequence" />
<field name="rate" />
<field name="date" />
</group>
<group>
<group>
<field name="pending_principal_amount"/>
<field name="payment_amount"/>
<field name="principal_amount"/>
<field name="interests_amount"/>
<field name="final_pending_principal_amount"/>
<field name="pending_principal_amount" />
<field name="payment_amount" />
<field name="principal_amount" />
<field name="interests_amount" />
<field name="final_pending_principal_amount" />
</group>
<group>
<field name="long_term_pending_principal_amount"/>
<field name="long_term_principal_amount"/>
<field name="long_term_pending_principal_amount" />
<field name="long_term_principal_amount" />
</group>
</group>
</form>
</field>
</record>
<act_window
id="account_loan_action"
name="Loans"
res_model="account.loan"/>
<menuitem id="account_loan_menu"
parent="account.menu_finance_entries" sequence="80"
name="Loans"
action="account_loan_action"/>
<act_window id="account_loan_action" name="Loans" res_model="account.loan" />
<menuitem
id="account_loan_menu"
parent="account.menu_finance_entries"
sequence="80"
name="Loans"
action="account_loan_action"
/>
</odoo>

View File

@ -1,21 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2011 Alexis de Lattre <alexis.delattre@akretion.com>
Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="view_move_form" model="ir.ui.view">
<field name="name">Add to_be_reversed and reversal_id fields</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_move_form"/>
<field name="inherit_id" ref="account.view_move_form" />
<field name="arch" type="xml">
<field name="company_id" position="after">
<field name="loan_line_id"
attrs="{'invisible': [('loan_line_id', '=', False)]}"
readonly="True"/>
<field
name="loan_line_id"
attrs="{'invisible': [('loan_line_id', '=', False)]}"
readonly="True"
/>
</field>
</field>
</record>
</odoo>

View File

@ -5,45 +5,40 @@ from odoo import api, fields, models
class AccountLoanGenerateWizard(models.TransientModel):
_name = "account.loan.generate.wizard"
_description = 'Loan generate wizard'
_description = "Loan generate wizard"
date = fields.Date(
'Account Date',
"Account Date",
required=True,
help="Choose the period for which you want to automatically post the "
"depreciation lines of running assets",
default=fields.Date.context_today)
loan_type = fields.Selection([
('leasing', 'Leasings'),
('loan', 'Loans'),
], required=True, default='loan')
"depreciation lines of running assets",
default=fields.Date.context_today,
)
loan_type = fields.Selection(
[("leasing", "Leasings"), ("loan", "Loans"),], required=True, default="loan"
)
def run_leasing(self):
created_ids = self.env['account.loan'].generate_leasing_entries(
self.date
)
action = self.env.ref('account.action_invoice_tree2')
created_ids = self.env["account.loan"].generate_leasing_entries(self.date)
action = self.env.ref("account.action_invoice_tree2")
result = action.read()[0]
if len(created_ids) == 0:
return
result['domain'] = [
('id', 'in', created_ids),
('type', '=', 'in_invoice')
]
result["domain"] = [("id", "in", created_ids), ("type", "=", "in_invoice")]
return result
def run_loan(self):
created_ids = self.env['account.loan'].generate_loan_entries(self.date)
action = self.env.ref('account.action_move_line_form')
created_ids = self.env["account.loan"].generate_loan_entries(self.date)
action = self.env.ref("account.action_move_line_form")
result = action.read()[0]
if len(created_ids) == 0:
return
result['domain'] = [('id', 'in', created_ids)]
result["domain"] = [("id", "in", created_ids)]
return result
@api.multi
def run(self):
self.ensure_one()
if self.loan_type == 'leasing':
if self.loan_type == "leasing":
return self.run_leasing()
return self.run_loan()

View File

@ -1,37 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2011 Alexis de Lattre <alexis.delattre@akretion.com>
Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="account_loan_generate_wizard_form" model="ir.ui.view">
<field name="name">Pay amount</field>
<field name="model">account.loan.generate.wizard</field>
<field name="arch" type="xml">
<form string="Generate moves">
<group>
<field name="date"/>
<field name="loan_type"/>
<field name="date" />
<field name="loan_type" />
</group>
<footer>
<button name="run" string="Run"
type="object" class="oe_highlight"/>
<button
name="run"
string="Run"
type="object"
class="oe_highlight"
/>
or
<button string="Cancel" class="oe_link" special="cancel"/>
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="account_loan_generate_wizard_action" model="ir.actions.act_window">
<field name="name">Generate moves</field>
<field name="res_model">account.loan.generate.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem name="Generate Loan Entries" action="account_loan_generate_wizard_action"
id="account_loan_generate_wizard_menu"
parent="account.menu_finance_entries_generate_entries" sequence="111" groups="base.group_no_one"/>
<menuitem
name="Generate Loan Entries"
action="account_loan_generate_wizard_action"
id="account_loan_generate_wizard_menu"
parent="account.menu_finance_entries_generate_entries"
sequence="111"
groups="base.group_no_one"
/>
</odoo>

View File

@ -1,54 +1,42 @@
# Copyright 2018 Creu Blanca
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models, _
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class AccountLoan(models.TransientModel):
_name = 'account.loan.pay.amount'
_description = 'Loan pay amount'
_name = "account.loan.pay.amount"
_description = "Loan pay amount"
loan_id = fields.Many2one(
'account.loan',
required=True,
readonly=True,
)
loan_id = fields.Many2one("account.loan", required=True, readonly=True,)
currency_id = fields.Many2one(
'res.currency',
related='loan_id.currency_id',
readonly=True
)
cancel_loan = fields.Boolean(
default=False,
"res.currency", related="loan_id.currency_id", readonly=True
)
cancel_loan = fields.Boolean(default=False,)
date = fields.Date(required=True, default=fields.Date.today())
amount = fields.Monetary(
currency_field='currency_id',
string='Amount to reduce from Principal',
)
fees = fields.Monetary(
currency_field='currency_id',
string='Bank fees'
currency_field="currency_id", string="Amount to reduce from Principal",
)
fees = fields.Monetary(currency_field="currency_id", string="Bank fees")
@api.onchange('cancel_loan')
@api.onchange("cancel_loan")
def _onchange_cancel_loan(self):
if self.cancel_loan:
self.amount = max(self.loan_id.line_ids.filtered(
lambda r: not r.move_ids and not r.invoice_ids).mapped(
'pending_principal_amount'
)
self.amount = max(
self.loan_id.line_ids.filtered(
lambda r: not r.move_ids and not r.invoice_ids
).mapped("pending_principal_amount")
)
def new_line_vals(self, sequence):
return {
'loan_id': self.loan_id.id,
'sequence': sequence,
'payment_amount': self.amount + self.fees,
'rate': 0,
'interests_amount': self.fees,
'date': self.date,
"loan_id": self.loan_id.id,
"sequence": sequence,
"payment_amount": self.amount + self.fees,
"rate": 0,
"interests_amount": self.fees,
"date": self.date,
}
@api.multi
@ -56,41 +44,40 @@ class AccountLoan(models.TransientModel):
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.invoice_ids
lambda r: r.date < self.date and not r.invoice_ids
):
raise UserError(_('Some invoices are not created'))
raise UserError(_("Some invoices are not created"))
if self.loan_id.line_ids.filtered(
lambda r: r.date > self.date and r.invoice_ids
lambda r: r.date > self.date and r.invoice_ids
):
raise UserError(_('Some future invoices already exists'))
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
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'))
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
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, pending) == 1:
raise UserError(_('Amount cannot be bigger than debt'))
raise UserError(_("Amount cannot be bigger than debt"))
if self.loan_id.currency_id.compare_amounts(self.amount, 0) <= 0:
raise UserError(_('Amount cannot be less than zero'))
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 = 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)
old_line.long_term_pending_principal_amount
)
amount = self.loan_id.loan_amount
for line in self.loan_id.line_ids.sorted('sequence'):
for line in self.loan_id.line_ids.sorted("sequence"):
if line.move_ids:
amount = line.final_pending_principal_amount
else:
@ -102,5 +89,5 @@ class AccountLoan(models.TransientModel):
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'})
self.loan_id.write({"state": "cancelled"})
return new_line.view_process_values()

View File

@ -1,35 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2011 Alexis de Lattre <alexis.delattre@akretion.com>
Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="account_loan_pay_amount_form" model="ir.ui.view">
<field name="name">Pay amount</field>
<field name="model">account.loan.pay.amount</field>
<field name="arch" type="xml">
<form string="Pay amount">
<group>
<field name="loan_id" readonly="True"/>
<field name="date"/>
<field name="cancel_loan"/>
<field name="amount"/>
<field name="fees"/>
<field name="currency_id"/>
<field name="loan_id" readonly="True" />
<field name="date" />
<field name="cancel_loan" />
<field name="amount" />
<field name="fees" />
<field name="currency_id" />
</group>
<footer>
<button name="run" string="Run"
type="object" class="oe_highlight"/>
<button
name="run"
string="Run"
type="object"
class="oe_highlight"
/>
or
<button string="Cancel" class="oe_link" special="cancel"/>
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="account_loan_pay_amount_action" model="ir.actions.act_window">
<field name="name">Pay amount</field>
<field name="res_model">account.loan.pay.amount</field>
@ -37,5 +36,4 @@
<field name="target">new</field>
<field name="context">{'default_loan_id': active_id}</field>
</record>
</odoo>

View File

@ -1,24 +1,24 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class AccountLoanPost(models.TransientModel):
_name = "account.loan.post"
_description = 'Loan post'
_description = "Loan post"
@api.model
def _default_journal_id(self):
loan_id = self._context.get('default_loan_id')
loan_id = self._context.get("default_loan_id")
if loan_id:
return self.env['account.loan'].browse(loan_id).journal_id.id
return self.env["account.loan"].browse(loan_id).journal_id.id
@api.model
def _default_account_id(self):
loan_id = self._context.get('default_loan_id')
loan_id = self._context.get("default_loan_id")
if loan_id:
loan = self.env['account.loan'].browse(loan_id)
loan = self.env["account.loan"].browse(loan_id)
if loan.is_leasing:
return loan.leased_asset_account_id.id
else:
@ -26,69 +26,67 @@ class AccountLoanPost(models.TransientModel):
force_company=loan.company_id.id
).property_account_receivable_id.id
loan_id = fields.Many2one(
'account.loan',
required=True,
readonly=True,
)
loan_id = fields.Many2one("account.loan", required=True, readonly=True,)
journal_id = fields.Many2one(
'account.journal',
required=True,
default=_default_journal_id
"account.journal", required=True, default=_default_journal_id
)
account_id = fields.Many2one(
'account.account',
required=True,
default=_default_account_id
"account.account", required=True, default=_default_account_id
)
def move_line_vals(self):
res = list()
partner = self.loan_id.partner_id.with_context(
force_company=self.loan_id.company_id.id)
force_company=self.loan_id.company_id.id
)
line = self.loan_id.line_ids.filtered(lambda r: r.sequence == 1)
res.append({
'account_id': self.account_id.id,
'partner_id': partner.id,
'credit': 0,
'debit': line.pending_principal_amount,
})
res.append(
{
"account_id": self.account_id.id,
"partner_id": partner.id,
"credit": 0,
"debit": line.pending_principal_amount,
}
)
if line.pending_principal_amount - line.long_term_pending_principal_amount > 0:
res.append(
{
"account_id": self.loan_id.short_term_loan_account_id.id,
"credit": (
line.pending_principal_amount
- line.long_term_pending_principal_amount
),
"debit": 0,
}
)
if (
line.pending_principal_amount -
line.long_term_pending_principal_amount > 0
and self.loan_id.long_term_loan_account_id
):
res.append({
'account_id': self.loan_id.short_term_loan_account_id.id,
'credit': (line.pending_principal_amount -
line.long_term_pending_principal_amount),
'debit': 0,
})
if (
line.long_term_pending_principal_amount > 0 and
self.loan_id.long_term_loan_account_id
):
res.append({
'account_id': self.loan_id.long_term_loan_account_id.id,
'credit': line.long_term_pending_principal_amount,
'debit': 0,
})
res.append(
{
"account_id": self.loan_id.long_term_loan_account_id.id,
"credit": line.long_term_pending_principal_amount,
"debit": 0,
}
)
return res
def move_vals(self):
return {
'loan_id': self.loan_id.id,
'date': self.loan_id.start_date,
'ref': self.loan_id.name,
'journal_id': self.journal_id.id,
'line_ids': [(0, 0, vals) for vals in self.move_line_vals()]
"loan_id": self.loan_id.id,
"date": self.loan_id.start_date,
"ref": self.loan_id.name,
"journal_id": self.journal_id.id,
"line_ids": [(0, 0, vals) for vals in self.move_line_vals()],
}
@api.multi
def run(self):
self.ensure_one()
if self.loan_id.state != 'draft':
raise UserError(_('Only loans in draft state can be posted'))
if self.loan_id.state != "draft":
raise UserError(_("Only loans in draft state can be posted"))
self.loan_id.post()
move = self.env['account.move'].create(self.move_vals())
move = self.env["account.move"].create(self.move_vals())
move.post()

View File

@ -1,32 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2011 Alexis de Lattre <alexis.delattre@akretion.com>
Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="account_loan_post_form" model="ir.ui.view">
<field name="name">Post loan</field>
<field name="model">account.loan.post</field>
<field name="arch" type="xml">
<form string="Pay amount">
<group>
<field name="loan_id" readonly="True"/>
<field name="account_id"/>
<field name="journal_id"/>
<field name="loan_id" readonly="True" />
<field name="account_id" />
<field name="journal_id" />
</group>
<footer>
<button name="run" string="Run"
type="object" class="oe_highlight"/>
<button
name="run"
string="Run"
type="object"
class="oe_highlight"
/>
or
<button string="Cancel" class="oe_link" special="cancel"/>
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="account_loan_post_action" model="ir.actions.act_window">
<field name="name">Post loan</field>
<field name="res_model">account.loan.post</field>
@ -34,5 +33,4 @@
<field name="target">new</field>
<field name="context">{'default_loan_id': active_id}</field>
</record>
</odoo>