[IMP] account_loan: black,isort,prettier
This commit is contained in:
parent
6a2179377e
commit
e6972252f5
@ -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',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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()
|
||||||
|
@ -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>
|
||||||
|
@ -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()
|
||||||
|
@ -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>
|
||||||
|
@ -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()
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user