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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,5 +8,4 @@
['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
</field> </field>
</record> </record>
</odoo> </odoo>

View File

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

View File

@ -3,7 +3,6 @@
Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com> Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
<record id="account_loan_tree" model="ir.ui.view"> <record id="account_loan_tree" model="ir.ui.view">
<field name="name">account.loan.tree</field> <field name="name">account.loan.tree</field>
<field name="model">account.loan</field> <field name="model">account.loan</field>
@ -16,44 +15,54 @@
</tree> </tree>
</field> </field>
</record> </record>
<record id="account_loan_form" model="ir.ui.view"> <record id="account_loan_form" model="ir.ui.view">
<field name="name">account.loan.form</field> <field name="name">account.loan.form</field>
<field name="model">account.loan</field> <field name="model">account.loan</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Loan"> <form string="Loan">
<header> <header>
<button name="compute_lines" type="object" <button name="compute_lines" type="object" string="Compute items" />
string="Compute items"/> <button
<button name="%(account_loan_post_action)d" name="%(account_loan_post_action)d"
states="draft" type="action" states="draft"
string="Post"/> type="action"
string="Post"
/>
<field name="state" widget="statusbar" /> <field name="state" widget="statusbar" />
</header> </header>
<sheet> <sheet>
<div class="oe_button_box"> <div class="oe_button_box">
<button name="view_account_moves" <button
name="view_account_moves"
class="oe_stat_button" class="oe_stat_button"
icon="fa-bars" icon="fa-bars"
attrs="{'invisible': [('state', '=', 'draft')]}" attrs="{'invisible': [('state', '=', 'draft')]}"
type="object" string="Moves"/> type="object"
<button name="view_account_invoices" string="Moves"
/>
<button
name="view_account_invoices"
class="oe_stat_button" class="oe_stat_button"
icon="fa-pencil-square-o" icon="fa-pencil-square-o"
attrs="{'invisible': ['|', ('state', '=', 'draft'), ('is_leasing', '=', False)]}" attrs="{'invisible': ['|', ('state', '=', 'draft'), ('is_leasing', '=', False)]}"
type="object" string="Invoices"/> type="object"
<button name="%(account_loan_pay_amount_action)d" string="Invoices"
class="oe_stat_button" icon="fa-usd" />
<button
name="%(account_loan_pay_amount_action)d"
class="oe_stat_button"
icon="fa-usd"
attrs="{'invisible': [('state', '!=', 'posted')]}" attrs="{'invisible': [('state', '!=', 'posted')]}"
type="action" string="Pay amount"/> type="action"
string="Pay amount"
/>
</div> </div>
<h1> <h1>
<field name="name" /> <field name="name" />
</h1> </h1>
<group> <group>
<group> <group>
<field name="company_id" <field name="company_id" options="{'no_create': True}" />
options="{'no_create': True}"/>
<field name="loan_type" /> <field name="loan_type" />
<field name="loan_amount" /> <field name="loan_amount" />
</group> </group>
@ -101,19 +110,28 @@
</group> </group>
</group> </group>
</page> </page>
<page string="Leasing" id="leasing" <page
attrs="{'invisible': [('is_leasing', '=', False)]}"> string="Leasing"
id="leasing"
attrs="{'invisible': [('is_leasing', '=', False)]}"
>
<group> <group>
<group> <group>
<field name="leased_asset_account_id" <field
attrs="{'required': [('is_leasing', '=', True)]}"/> name="leased_asset_account_id"
attrs="{'required': [('is_leasing', '=', True)]}"
/>
<field name="residual_amount" /> <field name="residual_amount" />
</group> </group>
<group> <group>
<field name="product_id" <field
attrs="{'required': [('is_leasing', '=', True)]}"/> name="product_id"
<field name="interests_product_id" attrs="{'required': [('is_leasing', '=', True)]}"
attrs="{'required': [('is_leasing', '=', True)]}"/> />
<field
name="interests_product_id"
attrs="{'required': [('is_leasing', '=', True)]}"
/>
<field name="post_invoice" /> <field name="post_invoice" />
</group> </group>
</group> </group>
@ -121,15 +139,13 @@
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_follower_ids" <field name="message_follower_ids" widget="mail_followers" />
widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity" /> <field name="activity_ids" widget="mail_activity" />
<field name="message_ids" widget="mail_thread" /> <field name="message_ids" widget="mail_thread" />
</div> </div>
</form> </form>
</field> </field>
</record> </record>
<record id="account_loan_line_tree" model="ir.ui.view"> <record id="account_loan_line_tree" model="ir.ui.view">
<field name="name">account.loan.line.tree</field> <field name="name">account.loan.line.tree</field>
<field name="model">account.loan.line</field> <field name="model">account.loan.line</field>
@ -142,26 +158,37 @@
<field name="payment_amount" sum="Total payments" /> <field name="payment_amount" sum="Total payments" />
<field name="principal_amount" /> <field name="principal_amount" />
<field name="interests_amount" sum="Total interests" /> <field name="interests_amount" sum="Total interests" />
<field name="long_term_pending_principal_amount" <field
attrs="{'invisible': [('long_term_loan_account_id', '=', False)]}"/> name="long_term_pending_principal_amount"
<field name="long_term_principal_amount" attrs="{'invisible': [('long_term_loan_account_id', '=', False)]}"
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="long_term_loan_account_id" invisible="1" />
<field name="loan_state" invisible="1" /> <field name="loan_state" invisible="1" />
<field name="is_leasing" invisible="1" /> <field name="is_leasing" invisible="1" />
<field name="has_invoices" invisible="1" /> <field name="has_invoices" invisible="1" />
<field name="has_moves" invisible="1" /> <field name="has_moves" invisible="1" />
<field name="currency_id" invisible="1" /> <field name="currency_id" invisible="1" />
<button name="view_account_values" string="Values" <button
type="object" icon="fa-eye" name="view_account_values"
attrs="{'invisible': [('has_moves', '=', False), ('has_invoices', '=', False)]}"/> string="Values"
<button name="view_process_values" string="Process" type="object"
type="object" icon="fa-cogs" icon="fa-eye"
attrs="{'invisible': ['|', '|', ('has_moves', '=', True), ('has_invoices', '=', True), ('loan_state', '!=', 'posted')]}"/> 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> </tree>
</field> </field>
</record> </record>
<record id="account_loan_line_form" model="ir.ui.view"> <record id="account_loan_line_form" model="ir.ui.view">
<field name="name">account.loan.line.form</field> <field name="name">account.loan.line.form</field>
<field name="model">account.loan.line</field> <field name="model">account.loan.line</field>
@ -188,15 +215,12 @@
</form> </form>
</field> </field>
</record> </record>
<act_window id="account_loan_action" name="Loans" res_model="account.loan" />
<act_window <menuitem
id="account_loan_action" id="account_loan_menu"
parent="account.menu_finance_entries"
sequence="80"
name="Loans" name="Loans"
res_model="account.loan"/> action="account_loan_action"
/>
<menuitem id="account_loan_menu"
parent="account.menu_finance_entries" sequence="80"
name="Loans"
action="account_loan_action"/>
</odoo> </odoo>

View File

@ -3,19 +3,18 @@
Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com> Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
<record id="view_move_form" model="ir.ui.view"> <record id="view_move_form" model="ir.ui.view">
<field name="name">Add to_be_reversed and reversal_id fields</field> <field name="name">Add to_be_reversed and reversal_id fields</field>
<field name="model">account.move</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="arch" type="xml">
<field name="company_id" position="after"> <field name="company_id" position="after">
<field name="loan_line_id" <field
name="loan_line_id"
attrs="{'invisible': [('loan_line_id', '=', False)]}" attrs="{'invisible': [('loan_line_id', '=', False)]}"
readonly="True"/> readonly="True"
/>
</field> </field>
</field> </field>
</record> </record>
</odoo> </odoo>

View File

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

View File

@ -3,8 +3,6 @@
Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com> Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
<record id="account_loan_generate_wizard_form" model="ir.ui.view"> <record id="account_loan_generate_wizard_form" model="ir.ui.view">
<field name="name">Pay amount</field> <field name="name">Pay amount</field>
<field name="model">account.loan.generate.wizard</field> <field name="model">account.loan.generate.wizard</field>
@ -15,23 +13,30 @@
<field name="loan_type" /> <field name="loan_type" />
</group> </group>
<footer> <footer>
<button name="run" string="Run" <button
type="object" class="oe_highlight"/> name="run"
string="Run"
type="object"
class="oe_highlight"
/>
or or
<button string="Cancel" class="oe_link" special="cancel" /> <button string="Cancel" class="oe_link" special="cancel" />
</footer> </footer>
</form> </form>
</field> </field>
</record> </record>
<record id="account_loan_generate_wizard_action" model="ir.actions.act_window"> <record id="account_loan_generate_wizard_action" model="ir.actions.act_window">
<field name="name">Generate moves</field> <field name="name">Generate moves</field>
<field name="res_model">account.loan.generate.wizard</field> <field name="res_model">account.loan.generate.wizard</field>
<field name="view_mode">form</field> <field name="view_mode">form</field>
<field name="target">new</field> <field name="target">new</field>
</record> </record>
<menuitem
<menuitem name="Generate Loan Entries" action="account_loan_generate_wizard_action" name="Generate Loan Entries"
action="account_loan_generate_wizard_action"
id="account_loan_generate_wizard_menu" id="account_loan_generate_wizard_menu"
parent="account.menu_finance_entries_generate_entries" sequence="111" groups="base.group_no_one"/> parent="account.menu_finance_entries_generate_entries"
sequence="111"
groups="base.group_no_one"
/>
</odoo> </odoo>

View File

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

View File

@ -3,8 +3,6 @@
Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com> Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
<record id="account_loan_pay_amount_form" model="ir.ui.view"> <record id="account_loan_pay_amount_form" model="ir.ui.view">
<field name="name">Pay amount</field> <field name="name">Pay amount</field>
<field name="model">account.loan.pay.amount</field> <field name="model">account.loan.pay.amount</field>
@ -19,17 +17,18 @@
<field name="currency_id" /> <field name="currency_id" />
</group> </group>
<footer> <footer>
<button name="run" string="Run" <button
type="object" class="oe_highlight"/> name="run"
string="Run"
type="object"
class="oe_highlight"
/>
or or
<button string="Cancel" class="oe_link" special="cancel" /> <button string="Cancel" class="oe_link" special="cancel" />
</footer> </footer>
</form> </form>
</field> </field>
</record> </record>
<record id="account_loan_pay_amount_action" model="ir.actions.act_window"> <record id="account_loan_pay_amount_action" model="ir.actions.act_window">
<field name="name">Pay amount</field> <field name="name">Pay amount</field>
<field name="res_model">account.loan.pay.amount</field> <field name="res_model">account.loan.pay.amount</field>
@ -37,5 +36,4 @@
<field name="target">new</field> <field name="target">new</field>
<field name="context">{'default_loan_id': active_id}</field> <field name="context">{'default_loan_id': active_id}</field>
</record> </record>
</odoo> </odoo>

View File

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

View File

@ -3,8 +3,6 @@
Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com> Copyright 2016 Antonio Espinosa <antonio.espinosa@tecnativa.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
<record id="account_loan_post_form" model="ir.ui.view"> <record id="account_loan_post_form" model="ir.ui.view">
<field name="name">Post loan</field> <field name="name">Post loan</field>
<field name="model">account.loan.post</field> <field name="model">account.loan.post</field>
@ -16,17 +14,18 @@
<field name="journal_id" /> <field name="journal_id" />
</group> </group>
<footer> <footer>
<button name="run" string="Run" <button
type="object" class="oe_highlight"/> name="run"
string="Run"
type="object"
class="oe_highlight"
/>
or or
<button string="Cancel" class="oe_link" special="cancel" /> <button string="Cancel" class="oe_link" special="cancel" />
</footer> </footer>
</form> </form>
</field> </field>
</record> </record>
<record id="account_loan_post_action" model="ir.actions.act_window"> <record id="account_loan_post_action" model="ir.actions.act_window">
<field name="name">Post loan</field> <field name="name">Post loan</field>
<field name="res_model">account.loan.post</field> <field name="res_model">account.loan.post</field>
@ -34,5 +33,4 @@
<field name="target">new</field> <field name="target">new</field>
<field name="context">{'default_loan_id': active_id}</field> <field name="context">{'default_loan_id': active_id}</field>
</record> </record>
</odoo> </odoo>