From e6972252f5ded43c6b48fe3bbd7d8424fa18bdbb Mon Sep 17 00:00:00 2001 From: Alba Riera Date: Tue, 23 Feb 2021 11:00:15 +0100 Subject: [PATCH] [IMP] account_loan: black,isort,prettier --- account_loan/__manifest__.py | 28 +- account_loan/data/ir_sequence_data.xml | 4 - account_loan/model/account_invoice.py | 48 +- account_loan/model/account_loan.py | 390 ++++++++-------- account_loan/model/account_loan_line.py | 428 +++++++++--------- account_loan/model/account_move.py | 15 +- .../security/account_loan_security.xml | 7 +- account_loan/tests/test_loan.py | 401 ++++++++-------- account_loan/views/account_loan_view.xml | 248 +++++----- account_loan/views/account_move_view.xml | 15 +- .../wizard/account_loan_generate_entries.py | 35 +- .../account_loan_generate_entries_view.xml | 31 +- .../wizard/account_loan_pay_amount.py | 89 ++-- .../wizard/account_loan_pay_amount_view.xml | 30 +- account_loan/wizard/account_loan_post.py | 94 ++-- .../wizard/account_loan_post_view.xml | 24 +- 16 files changed, 926 insertions(+), 961 deletions(-) diff --git a/account_loan/__manifest__.py b/account_loan/__manifest__.py index 028568880..f272f42c9 100644 --- a/account_loan/__manifest__.py +++ b/account_loan/__manifest__.py @@ -7,23 +7,17 @@ "website": "http://github.com/OCA/account-financial-tools", "license": "AGPL-3", "category": "Accounting", - "depends": [ - "account" - ], + "depends": ["account"], "data": [ - 'data/ir_sequence_data.xml', - 'security/ir.model.access.csv', - 'security/account_loan_security.xml', - 'wizard/account_loan_generate_entries_view.xml', - 'wizard/account_loan_pay_amount_view.xml', - 'wizard/account_loan_post_view.xml', - 'views/account_loan_view.xml', - 'views/account_move_view.xml', + "data/ir_sequence_data.xml", + "security/ir.model.access.csv", + "security/account_loan_security.xml", + "wizard/account_loan_generate_entries_view.xml", + "wizard/account_loan_pay_amount_view.xml", + "wizard/account_loan_post_view.xml", + "views/account_loan_view.xml", + "views/account_move_view.xml", ], - 'installable': True, - 'external_dependencies': { - 'python': [ - 'numpy', - ], - }, + "installable": True, + "external_dependencies": {"python": ["numpy",],}, } diff --git a/account_loan/data/ir_sequence_data.xml b/account_loan/data/ir_sequence_data.xml index fbbd1d95d..8f41ec9c5 100644 --- a/account_loan/data/ir_sequence_data.xml +++ b/account_loan/data/ir_sequence_data.xml @@ -1,18 +1,14 @@ - - - Account loan sequence account.loan ACL 6 - diff --git a/account_loan/model/account_invoice.py b/account_loan/model/account_invoice.py index 1b3f571cc..c60e86aa0 100644 --- a/account_loan/model/account_invoice.py +++ b/account_loan/model/account_invoice.py @@ -5,18 +5,13 @@ from odoo import api, fields, models class AccountInvoice(models.Model): - _inherit = 'account.invoice' + _inherit = "account.invoice" loan_line_id = fields.Many2one( - 'account.loan.line', - readonly=True, - ondelete='restrict', + "account.loan.line", readonly=True, ondelete="restrict", ) loan_id = fields.Many2one( - 'account.loan', - readonly=True, - store=True, - ondelete='restrict', + "account.loan", readonly=True, store=True, ondelete="restrict", ) @api.multi @@ -24,18 +19,27 @@ class AccountInvoice(models.Model): vals = super().finalize_invoice_move_lines(move_lines) if self.loan_line_id: ll = self.loan_line_id - if ( - ll.long_term_loan_account_id and - ll.long_term_principal_amount != 0 - ): - vals.append((0, 0, { - 'account_id': ll.loan_id.short_term_loan_account_id.id, - 'credit': ll.long_term_principal_amount, - 'debit': 0, - })) - vals.append((0, 0, { - 'account_id': ll.long_term_loan_account_id.id, - 'credit': 0, - 'debit': ll.long_term_principal_amount, - })) + if ll.long_term_loan_account_id and ll.long_term_principal_amount != 0: + vals.append( + ( + 0, + 0, + { + "account_id": ll.loan_id.short_term_loan_account_id.id, + "credit": ll.long_term_principal_amount, + "debit": 0, + }, + ) + ) + vals.append( + ( + 0, + 0, + { + "account_id": ll.long_term_loan_account_id.id, + "credit": 0, + "debit": ll.long_term_principal_amount, + }, + ) + ) return vals diff --git a/account_loan/model/account_loan.py b/account_loan/model/account_loan.py index 73ab196a1..5a4fe4fc0 100644 --- a/account_loan/model/account_loan.py +++ b/account_loan/model/account_loan.py @@ -1,12 +1,13 @@ # Copyright 2018 Creu Blanca # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import api, fields, models - -from datetime import datetime -from dateutil.relativedelta import relativedelta import logging import math +from datetime import datetime + +from dateutil.relativedelta import relativedelta + +from odoo import api, fields, models _logger = logging.getLogger(__name__) try: @@ -16,12 +17,12 @@ except (ImportError, IOError) as err: class AccountLoan(models.Model): - _name = 'account.loan' - _description = 'Loan' - _inherit = ['mail.thread', 'mail.activity.mixin'] + _name = "account.loan" + _description = "Loan" + _inherit = ["mail.thread", "mail.activity.mixin"] def _default_company(self): - force_company = self._context.get('force_company') + force_company = self._context.get("force_company") if not force_company: return self.env.user.company_id.id return force_company @@ -30,243 +31,218 @@ class AccountLoan(models.Model): copy=False, required=True, readonly=True, - default='/', - states={'draft': [('readonly', False)]}, + default="/", + states={"draft": [("readonly", False)]}, ) partner_id = fields.Many2one( - 'res.partner', + "res.partner", required=True, - string='Lender', - help='Company or individual that lends the money at an interest rate.', + string="Lender", + help="Company or individual that lends the money at an interest rate.", readonly=True, - states={'draft': [('readonly', False)]}, + states={"draft": [("readonly", False)]}, ) company_id = fields.Many2one( - 'res.company', + "res.company", required=True, default=_default_company, readonly=True, - states={'draft': [('readonly', False)]}, + states={"draft": [("readonly", False)]}, ) - state = fields.Selection([ - ('draft', 'Draft'), - ('posted', 'Posted'), - ('cancelled', 'Cancelled'), - ('closed', 'Closed'), - ], required=True, copy=False, default='draft') - line_ids = fields.One2many( - 'account.loan.line', - readonly=True, - inverse_name='loan_id', + state = fields.Selection( + [ + ("draft", "Draft"), + ("posted", "Posted"), + ("cancelled", "Cancelled"), + ("closed", "Closed"), + ], + required=True, copy=False, + default="draft", + ) + line_ids = fields.One2many( + "account.loan.line", readonly=True, inverse_name="loan_id", copy=False, ) periods = fields.Integer( required=True, readonly=True, - states={'draft': [('readonly', False)]}, - help='Number of periods that the loan will last', + states={"draft": [("readonly", False)]}, + help="Number of periods that the loan will last", ) method_period = fields.Integer( - string='Period Length', + string="Period Length", default=1, help="State here the time between 2 depreciations, in months", required=True, readonly=True, - states={'draft': [('readonly', False)]}, + states={"draft": [("readonly", False)]}, ) start_date = fields.Date( - help='Start of the moves', + help="Start of the moves", readonly=True, - states={'draft': [('readonly', False)]}, + states={"draft": [("readonly", False)]}, copy=False, ) rate = fields.Float( required=True, default=0.0, digits=(8, 6), - help='Currently applied rate', - track_visibility='always', + help="Currently applied rate", + track_visibility="always", ) rate_period = fields.Float( - compute='_compute_rate_period', digits=(8, 6), - help='Real rate that will be applied on each period', + compute="_compute_rate_period", + digits=(8, 6), + help="Real rate that will be applied on each period", ) rate_type = fields.Selection( - [ - ('napr', 'Nominal APR'), - ('ear', 'EAR'), - ('real', 'Real rate'), - ], + [("napr", "Nominal APR"), ("ear", "EAR"), ("real", "Real rate"),], required=True, - help='Method of computation of the applied rate', - default='napr', + help="Method of computation of the applied rate", + default="napr", readonly=True, - states={'draft': [('readonly', False)]}, + states={"draft": [("readonly", False)]}, ) loan_type = fields.Selection( [ - ('fixed-annuity', 'Fixed Annuity'), - ('fixed-annuity-begin', 'Fixed Annuity Begin'), - ('fixed-principal', 'Fixed Principal'), - ('interest', 'Only interest'), + ("fixed-annuity", "Fixed Annuity"), + ("fixed-annuity-begin", "Fixed Annuity Begin"), + ("fixed-principal", "Fixed Principal"), + ("interest", "Only interest"), ], required=True, - help='Method of computation of the period annuity', + help="Method of computation of the period annuity", readonly=True, - states={'draft': [('readonly', False)]}, - default='fixed-annuity' + states={"draft": [("readonly", False)]}, + default="fixed-annuity", ) fixed_amount = fields.Monetary( - currency_field='currency_id', - compute='_compute_fixed_amount', + currency_field="currency_id", compute="_compute_fixed_amount", ) fixed_loan_amount = fields.Monetary( - currency_field='currency_id', - readonly=True, - copy=False, - default=0, - ) - fixed_periods = fields.Integer( - readonly=True, - copy=False, - default=0, + currency_field="currency_id", readonly=True, copy=False, default=0, ) + fixed_periods = fields.Integer(readonly=True, copy=False, default=0,) loan_amount = fields.Monetary( - currency_field='currency_id', + currency_field="currency_id", required=True, readonly=True, - states={'draft': [('readonly', False)]}, + states={"draft": [("readonly", False)]}, ) residual_amount = fields.Monetary( - currency_field='currency_id', - default=0., + currency_field="currency_id", + default=0.0, required=True, readonly=True, - states={'draft': [('readonly', False)]}, - help='Residual amount of the lease that must be payed on the end in ' - 'order to acquire the asset', + states={"draft": [("readonly", False)]}, + help="Residual amount of the lease that must be payed on the end in " + "order to acquire the asset", ) round_on_end = fields.Boolean( default=False, - help='When checked, the differences will be applied on the last period' - ', if it is unchecked, the annuity will be recalculated on each ' - 'period.', + help="When checked, the differences will be applied on the last period" + ", if it is unchecked, the annuity will be recalculated on each " + "period.", readonly=True, - states={'draft': [('readonly', False)]}, + states={"draft": [("readonly", False)]}, ) payment_on_first_period = fields.Boolean( default=False, readonly=True, - states={'draft': [('readonly', False)]}, - help='When checked, the first payment will be on start date', + states={"draft": [("readonly", False)]}, + help="When checked, the first payment will be on start date", ) currency_id = fields.Many2one( - 'res.currency', - compute='_compute_currency', - readonly=True, + "res.currency", compute="_compute_currency", readonly=True, ) - journal_type = fields.Char(compute='_compute_journal_type') + journal_type = fields.Char(compute="_compute_journal_type") journal_id = fields.Many2one( - 'account.journal', + "account.journal", domain="[('company_id', '=', company_id),('type', '=', journal_type)]", required=True, readonly=True, - states={'draft': [('readonly', False)]}, + states={"draft": [("readonly", False)]}, ) short_term_loan_account_id = fields.Many2one( - 'account.account', + "account.account", domain="[('company_id', '=', company_id)]", - string='Short term account', - help='Account that will contain the pending amount on short term', + string="Short term account", + help="Account that will contain the pending amount on short term", required=True, readonly=True, - states={'draft': [('readonly', False)]}, + states={"draft": [("readonly", False)]}, ) long_term_loan_account_id = fields.Many2one( - 'account.account', - string='Long term account', - help='Account that will contain the pending amount on Long term', + "account.account", + string="Long term account", + help="Account that will contain the pending amount on Long term", domain="[('company_id', '=', company_id)]", readonly=True, - states={'draft': [('readonly', False)]}, + states={"draft": [("readonly", False)]}, ) interest_expenses_account_id = fields.Many2one( - 'account.account', + "account.account", domain="[('company_id', '=', company_id)]", - string='Interests account', - help='Account where the interests will be assigned to', + string="Interests account", + help="Account where the interests will be assigned to", required=True, readonly=True, - states={'draft': [('readonly', False)]}, + states={"draft": [("readonly", False)]}, ) is_leasing = fields.Boolean( - default=False, - readonly=True, - states={'draft': [('readonly', False)]}, + default=False, readonly=True, states={"draft": [("readonly", False)]}, ) leased_asset_account_id = fields.Many2one( - 'account.account', + "account.account", domain="[('company_id', '=', company_id)]", readonly=True, - states={'draft': [('readonly', False)]}, + states={"draft": [("readonly", False)]}, ) product_id = fields.Many2one( - 'product.product', - string='Loan product', - help='Product where the amount of the loan will be assigned when the ' - 'invoice is created', + "product.product", + string="Loan product", + help="Product where the amount of the loan will be assigned when the " + "invoice is created", ) interests_product_id = fields.Many2one( - 'product.product', - string='Interest product', - help='Product where the amount of interests will be assigned when the ' - 'invoice is created', - ) - move_ids = fields.One2many( - 'account.move', - copy=False, - inverse_name='loan_id' + "product.product", + string="Interest product", + help="Product where the amount of interests will be assigned when the " + "invoice is created", ) + move_ids = fields.One2many("account.move", copy=False, inverse_name="loan_id") pending_principal_amount = fields.Monetary( - currency_field='currency_id', - compute='_compute_total_amounts', + currency_field="currency_id", compute="_compute_total_amounts", ) payment_amount = fields.Monetary( - currency_field='currency_id', - string='Total payed amount', - compute='_compute_total_amounts', + currency_field="currency_id", + string="Total payed amount", + compute="_compute_total_amounts", ) interests_amount = fields.Monetary( - currency_field='currency_id', - string='Total interests payed', - compute='_compute_total_amounts', + currency_field="currency_id", + string="Total interests payed", + compute="_compute_total_amounts", ) post_invoice = fields.Boolean( - default=True, - help='Invoices will be posted automatically' + default=True, help="Invoices will be posted automatically" ) _sql_constraints = [ - ('name_uniq', 'unique(name, company_id)', - 'Loan name must be unique'), + ("name_uniq", "unique(name, company_id)", "Loan name must be unique"), ] - @api.depends('line_ids', 'currency_id', 'loan_amount') + @api.depends("line_ids", "currency_id", "loan_amount") def _compute_total_amounts(self): for record in self: lines = record.line_ids.filtered(lambda r: r.move_ids) - record.payment_amount = sum( - lines.mapped('payment_amount')) or 0. - record.interests_amount = sum( - lines.mapped('interests_amount')) or 0. + record.payment_amount = sum(lines.mapped("payment_amount")) or 0.0 + record.interests_amount = sum(lines.mapped("interests_amount")) or 0.0 record.pending_principal_amount = ( - record.loan_amount - - record.payment_amount + - record.interests_amount + record.loan_amount - record.payment_amount + record.interests_amount ) - @api.depends('rate_period', 'fixed_loan_amount', 'fixed_periods', - 'currency_id') + @api.depends("rate_period", "fixed_loan_amount", "fixed_periods", "currency_id") def _compute_fixed_amount(self): """ Computes the fixed amount in order to be used if round_on_end is @@ -275,25 +251,29 @@ class AccountLoan(models.Model): :return: """ for record in self: - if record.loan_type == 'fixed-annuity': - record.fixed_amount = - record.currency_id.round(numpy.pmt( - record.loan_rate() / 100, - record.fixed_periods, - record.fixed_loan_amount, - -record.residual_amount - )) - elif record.loan_type == 'fixed-annuity-begin': - record.fixed_amount = - record.currency_id.round(numpy.pmt( - record.loan_rate() / 100, - record.fixed_periods, - record.fixed_loan_amount, - -record.residual_amount, - when='begin' - )) - elif record.loan_type == 'fixed-principal': + if record.loan_type == "fixed-annuity": + record.fixed_amount = -record.currency_id.round( + numpy.pmt( + record.loan_rate() / 100, + record.fixed_periods, + record.fixed_loan_amount, + -record.residual_amount, + ) + ) + elif record.loan_type == "fixed-annuity-begin": + record.fixed_amount = -record.currency_id.round( + numpy.pmt( + record.loan_rate() / 100, + record.fixed_periods, + record.fixed_loan_amount, + -record.residual_amount, + when="begin", + ) + ) + elif record.loan_type == "fixed-principal": record.fixed_amount = record.currency_id.round( - (record.fixed_loan_amount - record.residual_amount) / - record.fixed_periods + (record.fixed_loan_amount - record.residual_amount) + / record.fixed_periods ) else: record.fixed_amount = 0.0 @@ -307,57 +287,58 @@ class AccountLoan(models.Model): :param method_period: Number of months between payments :return: """ - if rate_type == 'napr': + if rate_type == "napr": return rate / 12 * method_period - if rate_type == 'ear': + if rate_type == "ear": return math.pow(1 + rate, method_period / 12) - 1 return rate - @api.depends('rate', 'method_period', 'rate_type') + @api.depends("rate", "method_period", "rate_type") def _compute_rate_period(self): for record in self: record.rate_period = record.loan_rate() def loan_rate(self): - return self.compute_rate( - self.rate, self.rate_type, self.method_period - ) + return self.compute_rate(self.rate, self.rate_type, self.method_period) - @api.depends('journal_id', 'company_id') + @api.depends("journal_id", "company_id") def _compute_currency(self): for rec in self: - rec.currency_id = ( - rec.journal_id.currency_id or rec.company_id.currency_id) + rec.currency_id = rec.journal_id.currency_id or rec.company_id.currency_id - @api.depends('is_leasing') + @api.depends("is_leasing") def _compute_journal_type(self): for record in self: if record.is_leasing: - record.journal_type = 'purchase' + record.journal_type = "purchase" else: - record.journal_type = 'general' + record.journal_type = "general" - @api.onchange('is_leasing') + @api.onchange("is_leasing") def _onchange_is_leasing(self): - self.journal_id = self.env['account.journal'].search([ - ('company_id', '=', self.company_id.id), - ('type', '=', 'purchase' if self.is_leasing else 'general') - ], limit=1) + self.journal_id = self.env["account.journal"].search( + [ + ("company_id", "=", self.company_id.id), + ("type", "=", "purchase" if self.is_leasing else "general"), + ], + limit=1, + ) self.residual_amount = 0.0 - @api.onchange('company_id') + @api.onchange("company_id") def _onchange_company(self): self._onchange_is_leasing() - self.interest_expenses_account_id = self.short_term_loan_account_id = \ - self.long_term_loan_account_id = False + self.interest_expenses_account_id = ( + self.short_term_loan_account_id + ) = self.long_term_loan_account_id = False def get_default_name(self, vals): - return self.env['ir.sequence'].next_by_code('account.loan') or '/' + return self.env["ir.sequence"].next_by_code("account.loan") or "/" @api.model def create(self, vals): - if vals.get('name', '/') == '/': - vals['name'] = self.get_default_name(vals) + if vals.get("name", "/") == "/": + vals["name"] = self.get_default_name(vals) return super().create(vals) @api.multi @@ -366,16 +347,16 @@ class AccountLoan(models.Model): if not self.start_date: self.start_date = fields.Date.today() self.compute_draft_lines() - self.write({'state': 'posted'}) + self.write({"state": "posted"}) @api.multi def close(self): - self.write({'state': 'closed'}) + self.write({"state": "closed"}) @api.multi def compute_lines(self): self.ensure_one() - if self.state == 'draft': + if self.state == "draft": return self.compute_draft_lines() return self.compute_posted_lines() @@ -384,7 +365,7 @@ class AccountLoan(models.Model): Recompute the amounts of not finished lines. Useful if rate is changed """ amount = self.loan_amount - for line in self.line_ids.sorted('sequence'): + for line in self.line_ids.sorted("sequence"): if line.move_ids: amount = line.final_pending_principal_amount else: @@ -403,25 +384,27 @@ class AccountLoan(models.Model): amount = 0 if not lines: return - final_sequence = min(lines.mapped('sequence')) - for line in lines.sorted('sequence', reverse=True): + final_sequence = min(lines.mapped("sequence")) + for line in lines.sorted("sequence", reverse=True): date = line.date + relativedelta(months=12) - if self.state == 'draft' or line.sequence != final_sequence: + if self.state == "draft" or line.sequence != final_sequence: line.long_term_pending_principal_amount = sum( - self.line_ids.filtered( - lambda r: r.date >= date - ).mapped('principal_amount')) + self.line_ids.filtered(lambda r: r.date >= date).mapped( + "principal_amount" + ) + ) line.long_term_principal_amount = ( - line.long_term_pending_principal_amount - amount) + line.long_term_pending_principal_amount - amount + ) amount = line.long_term_pending_principal_amount def new_line_vals(self, sequence, date, amount): return { - 'loan_id': self.id, - 'sequence': sequence, - 'date': date, - 'pending_principal_amount': amount, - 'rate': self.rate_period, + "loan_id": self.id, + "sequence": sequence, + "date": date, + "pending_principal_amount": amount, + "rate": self.rate_period, } @api.multi @@ -439,7 +422,7 @@ class AccountLoan(models.Model): if not self.payment_on_first_period: date += delta for i in range(1, self.periods + 1): - line = self.env['account.loan.line'].create( + line = self.env["account.loan.line"].create( self.new_line_vals(i, date, amount) ) line.check_amount() @@ -451,20 +434,17 @@ class AccountLoan(models.Model): @api.multi def view_account_moves(self): self.ensure_one() - action = self.env.ref('account.action_move_line_form') + action = self.env.ref("account.action_move_line_form") result = action.read()[0] - result['domain'] = [('loan_id', '=', self.id)] + result["domain"] = [("loan_id", "=", self.id)] return result @api.multi def view_account_invoices(self): self.ensure_one() - action = self.env.ref('account.action_invoice_tree2') + action = self.env.ref("account.action_invoice_tree2") result = action.read()[0] - result['domain'] = [ - ('loan_id', '=', self.id), - ('type', '=', 'in_invoice') - ] + result["domain"] = [("loan_id", "=", self.id), ("type", "=", "in_invoice")] return result @api.model @@ -475,10 +455,9 @@ class AccountLoan(models.Model): :return: """ res = [] - for record in self.search([ - ('state', '=', 'posted'), - ('is_leasing', '=', False) - ]): + for record in self.search( + [("state", "=", "posted"), ("is_leasing", "=", False)] + ): lines = record.line_ids.filtered( lambda r: r.date <= date and not r.move_ids ) @@ -488,10 +467,9 @@ class AccountLoan(models.Model): @api.model def generate_leasing_entries(self, date): res = [] - for record in self.search([ - ('state', '=', 'posted'), - ('is_leasing', '=', True) - ]): + for record in self.search( + [("state", "=", "posted"), ("is_leasing", "=", True)] + ): res += record.line_ids.filtered( lambda r: r.date <= date and not r.invoice_ids ).generate_invoice() diff --git a/account_loan/model/account_loan_line.py b/account_loan/model/account_loan_line.py index 4bad7b384..9ff568172 100644 --- a/account_loan/model/account_loan_line.py +++ b/account_loan/model/account_loan_line.py @@ -1,10 +1,11 @@ # Copyright 2018 Creu Blanca # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import api, fields, models, _ -from odoo.exceptions import UserError import logging +from odoo import _, api, fields, models +from odoo.exceptions import UserError + _logger = logging.getLogger(__name__) try: import numpy @@ -13,128 +14,112 @@ except (ImportError, IOError) as err: class AccountLoanLine(models.Model): - _name = 'account.loan.line' - _description = 'Annuity' - _order = 'sequence asc' + _name = "account.loan.line" + _description = "Annuity" + _order = "sequence asc" - name = fields.Char(compute='_compute_name') + name = fields.Char(compute="_compute_name") loan_id = fields.Many2one( - 'account.loan', - required=True, + "account.loan", required=True, readonly=True, ondelete="cascade", + ) + is_leasing = fields.Boolean(related="loan_id.is_leasing", readonly=True,) + loan_type = fields.Selection( + [ + ("fixed-annuity", "Fixed Annuity"), + ("fixed-principal", "Fixed Principal"), + ("interest", "Only interest"), + ], + related="loan_id.loan_type", readonly=True, - ondelete='cascade', ) - is_leasing = fields.Boolean(related='loan_id.is_leasing', readonly=True, ) - loan_type = fields.Selection([ - ('fixed-annuity', 'Fixed Annuity'), - ('fixed-principal', 'Fixed Principal'), - ('interest', 'Only interest'), - ], related='loan_id.loan_type', readonly=True, + loan_state = fields.Selection( + [ + ("draft", "Draft"), + ("posted", "Posted"), + ("cancelled", "Cancelled"), + ("closed", "Closed"), + ], + related="loan_id.state", + readonly=True, + store=True, ) - loan_state = fields.Selection([ - ('draft', 'Draft'), - ('posted', 'Posted'), - ('cancelled', 'Cancelled'), - ('closed', 'Closed'), - ], related='loan_id.state', readonly=True, store=True) sequence = fields.Integer(required=True, readonly=True) date = fields.Date( - required=True, - readonly=True, - help='Date when the payment will be accounted', + required=True, readonly=True, help="Date when the payment will be accounted", ) long_term_loan_account_id = fields.Many2one( - 'account.account', - readony=True, - related='loan_id.long_term_loan_account_id', - ) - currency_id = fields.Many2one( - 'res.currency', - related='loan_id.currency_id', - ) - rate = fields.Float( - required=True, - readonly=True, - digits=(8, 6), + "account.account", readony=True, related="loan_id.long_term_loan_account_id", ) + currency_id = fields.Many2one("res.currency", related="loan_id.currency_id",) + rate = fields.Float(required=True, readonly=True, digits=(8, 6),) pending_principal_amount = fields.Monetary( - currency_field='currency_id', + currency_field="currency_id", readonly=True, - help='Pending amount of the loan before the payment', + help="Pending amount of the loan before the payment", ) long_term_pending_principal_amount = fields.Monetary( - currency_field='currency_id', + currency_field="currency_id", readonly=True, - help='Pending amount of the loan before the payment that will not be ' - 'payed in, at least, 12 months', + help="Pending amount of the loan before the payment that will not be " + "payed in, at least, 12 months", ) payment_amount = fields.Monetary( - currency_field='currency_id', + currency_field="currency_id", readonly=True, - help='Total amount that will be payed (Annuity)', + help="Total amount that will be payed (Annuity)", ) interests_amount = fields.Monetary( - currency_field='currency_id', + currency_field="currency_id", readonly=True, - help='Amount of the payment that will be assigned to interests', + help="Amount of the payment that will be assigned to interests", ) principal_amount = fields.Monetary( - currency_field='currency_id', - compute='_compute_amounts', - help='Amount of the payment that will reduce the pending loan amount', + currency_field="currency_id", + compute="_compute_amounts", + help="Amount of the payment that will reduce the pending loan amount", ) long_term_principal_amount = fields.Monetary( - currency_field='currency_id', + currency_field="currency_id", readonly=True, - help='Amount that will reduce the pending loan amount on long term', + help="Amount that will reduce the pending loan amount on long term", ) final_pending_principal_amount = fields.Monetary( - currency_field='currency_id', - compute='_compute_amounts', - help='Pending amount of the loan after the payment', - ) - move_ids = fields.One2many( - 'account.move', - inverse_name='loan_line_id', - ) - has_moves = fields.Boolean( - compute='_compute_has_moves' - ) - invoice_ids = fields.One2many( - 'account.invoice', - inverse_name='loan_line_id', - ) - has_invoices = fields.Boolean( - compute='_compute_has_invoices' + currency_field="currency_id", + compute="_compute_amounts", + help="Pending amount of the loan after the payment", ) + move_ids = fields.One2many("account.move", inverse_name="loan_line_id",) + has_moves = fields.Boolean(compute="_compute_has_moves") + invoice_ids = fields.One2many("account.invoice", inverse_name="loan_line_id",) + has_invoices = fields.Boolean(compute="_compute_has_invoices") _sql_constraints = [ - ('sequence_loan', - 'unique(loan_id, sequence)', - 'Sequence must be unique in a loan') + ( + "sequence_loan", + "unique(loan_id, sequence)", + "Sequence must be unique in a loan", + ) ] - @api.depends('move_ids') + @api.depends("move_ids") def _compute_has_moves(self): for record in self: record.has_moves = bool(record.move_ids) - @api.depends('invoice_ids') + @api.depends("invoice_ids") def _compute_has_invoices(self): for record in self: record.has_invoices = bool(record.invoice_ids) - @api.depends('loan_id.name', 'sequence') + @api.depends("loan_id.name", "sequence") def _compute_name(self): for record in self: - record.name = '%s-%d' % (record.loan_id.name, record.sequence) + record.name = "%s-%d" % (record.loan_id.name, record.sequence) - @api.depends('payment_amount', 'interests_amount', - 'pending_principal_amount') + @api.depends("payment_amount", "interests_amount", "pending_principal_amount") def _compute_amounts(self): for rec in self: rec.final_pending_principal_amount = ( - rec.pending_principal_amount - rec.payment_amount + - rec.interests_amount + rec.pending_principal_amount - rec.payment_amount + rec.interests_amount ) rec.principal_amount = rec.payment_amount - rec.interests_amount @@ -144,76 +129,76 @@ class AccountLoanLine(models.Model): :return: Amount to be payed on the annuity """ if self.sequence == self.loan_id.periods: - return (self.pending_principal_amount + self.interests_amount - - self.loan_id.residual_amount) - if self.loan_type == 'fixed-principal' and self.loan_id.round_on_end: - return self.loan_id.fixed_amount + self.interests_amount - if self.loan_type == 'fixed-principal': return ( - self.pending_principal_amount - - self.loan_id.residual_amount - ) / ( + self.pending_principal_amount + + self.interests_amount + - self.loan_id.residual_amount + ) + if self.loan_type == "fixed-principal" and self.loan_id.round_on_end: + return self.loan_id.fixed_amount + self.interests_amount + if self.loan_type == "fixed-principal": + return (self.pending_principal_amount - self.loan_id.residual_amount) / ( self.loan_id.periods - self.sequence + 1 ) + self.interests_amount - if self.loan_type == 'interest': + if self.loan_type == "interest": return self.interests_amount - if self.loan_type == 'fixed-annuity' and self.loan_id.round_on_end: + if self.loan_type == "fixed-annuity" and self.loan_id.round_on_end: return self.loan_id.fixed_amount - if self.loan_type == 'fixed-annuity': - return self.currency_id.round(- numpy.pmt( - self.loan_id.loan_rate() / 100, - self.loan_id.periods - self.sequence + 1, - self.pending_principal_amount, - -self.loan_id.residual_amount - )) - if ( - self.loan_type == 'fixed-annuity-begin' and - self.loan_id.round_on_end - ): + if self.loan_type == "fixed-annuity": + return self.currency_id.round( + -numpy.pmt( + self.loan_id.loan_rate() / 100, + self.loan_id.periods - self.sequence + 1, + self.pending_principal_amount, + -self.loan_id.residual_amount, + ) + ) + if self.loan_type == "fixed-annuity-begin" and self.loan_id.round_on_end: return self.loan_id.fixed_amount - if self.loan_type == 'fixed-annuity-begin': - return self.currency_id.round(- numpy.pmt( - self.loan_id.loan_rate() / 100, - self.loan_id.periods - self.sequence + 1, - self.pending_principal_amount, - -self.loan_id.residual_amount, - when='begin' - )) + if self.loan_type == "fixed-annuity-begin": + return self.currency_id.round( + -numpy.pmt( + self.loan_id.loan_rate() / 100, + self.loan_id.periods - self.sequence + 1, + self.pending_principal_amount, + -self.loan_id.residual_amount, + when="begin", + ) + ) def check_amount(self): """Recompute amounts if the annuity has not been processed""" if self.move_ids or self.invoice_ids: - raise UserError(_( - 'Amount cannot be recomputed if moves or invoices exists ' - 'already' - )) + raise UserError( + _("Amount cannot be recomputed if moves or invoices exists " "already") + ) if ( - self.sequence == self.loan_id.periods and - self.loan_id.round_on_end and - self.loan_type in ['fixed-annuity', 'fixed-annuity-begin'] + self.sequence == self.loan_id.periods + and self.loan_id.round_on_end + and self.loan_type in ["fixed-annuity", "fixed-annuity-begin"] ): self.interests_amount = self.currency_id.round( - self.loan_id.fixed_amount - self.pending_principal_amount + - self.loan_id.residual_amount + self.loan_id.fixed_amount + - self.pending_principal_amount + + self.loan_id.residual_amount ) self.payment_amount = self.currency_id.round(self.compute_amount()) elif not self.loan_id.round_on_end: - self.interests_amount = self.currency_id.round( - self.compute_interest()) + self.interests_amount = self.currency_id.round(self.compute_interest()) self.payment_amount = self.currency_id.round(self.compute_amount()) else: self.interests_amount = self.compute_interest() self.payment_amount = self.compute_amount() def compute_interest(self): - if self.loan_type == 'fixed-annuity-begin': + if self.loan_type == "fixed-annuity-begin": return -numpy.ipmt( self.loan_id.loan_rate() / 100, 2, self.loan_id.periods - self.sequence + 1, self.pending_principal_amount, -self.loan_id.residual_amount, - when='begin' + when="begin", ) return self.pending_principal_amount * self.loan_id.loan_rate() / 100 @@ -224,105 +209,118 @@ class AccountLoanLine(models.Model): :return: """ self.ensure_one() - interests_moves = self.move_ids.mapped('line_ids').filtered( + interests_moves = self.move_ids.mapped("line_ids").filtered( lambda r: r.account_id == self.loan_id.interest_expenses_account_id ) - short_term_moves = self.move_ids.mapped('line_ids').filtered( + short_term_moves = self.move_ids.mapped("line_ids").filtered( lambda r: r.account_id == self.loan_id.short_term_loan_account_id ) - long_term_moves = self.move_ids.mapped('line_ids').filtered( + long_term_moves = self.move_ids.mapped("line_ids").filtered( lambda r: r.account_id == self.loan_id.long_term_loan_account_id ) - self.interests_amount = ( - sum(interests_moves.mapped('debit')) - - sum(interests_moves.mapped('credit')) + self.interests_amount = sum(interests_moves.mapped("debit")) - sum( + interests_moves.mapped("credit") ) - self.long_term_principal_amount = ( - sum(long_term_moves.mapped('debit')) - - sum(long_term_moves.mapped('credit')) + self.long_term_principal_amount = sum(long_term_moves.mapped("debit")) - sum( + long_term_moves.mapped("credit") ) self.payment_amount = ( - sum(short_term_moves.mapped('debit')) - - sum(short_term_moves.mapped('credit')) + - self.long_term_principal_amount + - self.interests_amount + sum(short_term_moves.mapped("debit")) + - sum(short_term_moves.mapped("credit")) + + self.long_term_principal_amount + + self.interests_amount ) def move_vals(self): return { - 'loan_line_id': self.id, - 'loan_id': self.loan_id.id, - 'date': self.date, - 'ref': self.name, - 'journal_id': self.loan_id.journal_id.id, - 'line_ids': [(0, 0, vals) for vals in self.move_line_vals()] + "loan_line_id": self.id, + "loan_id": self.loan_id.id, + "date": self.date, + "ref": self.name, + "journal_id": self.loan_id.journal_id.id, + "line_ids": [(0, 0, vals) for vals in self.move_line_vals()], } def move_line_vals(self): vals = [] partner = self.loan_id.partner_id.with_context( - force_company=self.loan_id.company_id.id) - vals.append({ - 'account_id': partner.property_account_payable_id.id, - 'partner_id': partner.id, - 'credit': self.payment_amount, - 'debit': 0, - }) - vals.append({ - 'account_id': self.loan_id.interest_expenses_account_id.id, - 'credit': 0, - 'debit': self.interests_amount, - }) - vals.append({ - 'account_id': self.loan_id.short_term_loan_account_id.id, - 'credit': 0, - 'debit': self.payment_amount - self.interests_amount, - }) + force_company=self.loan_id.company_id.id + ) + vals.append( + { + "account_id": partner.property_account_payable_id.id, + "partner_id": partner.id, + "credit": self.payment_amount, + "debit": 0, + } + ) + vals.append( + { + "account_id": self.loan_id.interest_expenses_account_id.id, + "credit": 0, + "debit": self.interests_amount, + } + ) + vals.append( + { + "account_id": self.loan_id.short_term_loan_account_id.id, + "credit": 0, + "debit": self.payment_amount - self.interests_amount, + } + ) if self.long_term_loan_account_id and self.long_term_principal_amount: - vals.append({ - 'account_id': self.loan_id.short_term_loan_account_id.id, - 'credit': self.long_term_principal_amount, - 'debit': 0, - }) - vals.append({ - 'account_id': self.long_term_loan_account_id.id, - 'credit': 0, - 'debit': self.long_term_principal_amount, - }) + vals.append( + { + "account_id": self.loan_id.short_term_loan_account_id.id, + "credit": self.long_term_principal_amount, + "debit": 0, + } + ) + vals.append( + { + "account_id": self.long_term_loan_account_id.id, + "credit": 0, + "debit": self.long_term_principal_amount, + } + ) return vals def invoice_vals(self): partner = self.loan_id.partner_id.with_context( - force_company=self.loan_id.company_id.id) + force_company=self.loan_id.company_id.id + ) return { - 'loan_line_id': self.id, - 'loan_id': self.loan_id.id, - 'type': 'in_invoice', - 'partner_id': self.loan_id.partner_id.id, - 'date_invoice': self.date, - 'account_id': partner.property_account_payable_id.id, - 'journal_id': self.loan_id.journal_id.id, - 'company_id': self.loan_id.company_id.id, - 'invoice_line_ids': [(0, 0, vals) for vals in - self.invoice_line_vals()] + "loan_line_id": self.id, + "loan_id": self.loan_id.id, + "type": "in_invoice", + "partner_id": self.loan_id.partner_id.id, + "date_invoice": self.date, + "account_id": partner.property_account_payable_id.id, + "journal_id": self.loan_id.journal_id.id, + "company_id": self.loan_id.company_id.id, + "invoice_line_ids": [(0, 0, vals) for vals in self.invoice_line_vals()], } def invoice_line_vals(self): vals = list() - vals.append({ - 'product_id': self.loan_id.product_id.id, - 'name': self.loan_id.product_id.name, - 'quantity': 1, - 'price_unit': self.principal_amount, - 'account_id': self.loan_id.short_term_loan_account_id.id, - }) - vals.append({ - 'product_id': self.loan_id.interests_product_id.id, - 'name': self.loan_id.interests_product_id.name, - 'quantity': 1, - 'price_unit': self.interests_amount, - 'account_id': self.loan_id.interest_expenses_account_id.id, - }) + vals.append( + { + "product_id": self.loan_id.product_id.id, + "name": self.loan_id.product_id.name, + "quantity": 1, + "price_unit": self.principal_amount, + "account_id": self.loan_id.short_term_loan_account_id.id, + } + ) + vals.append( + { + "product_id": self.loan_id.interests_product_id.id, + "name": self.loan_id.interests_product_id.name, + "quantity": 1, + "price_unit": self.interests_amount, + "account_id": self.loan_id.interest_expenses_account_id.id, + } + ) return vals @api.multi @@ -335,10 +333,10 @@ class AccountLoanLine(models.Model): for record in self: if not record.move_ids: if record.loan_id.line_ids.filtered( - lambda r: r.date < record.date and not r.move_ids + lambda r: r.date < record.date and not r.move_ids ): - raise UserError(_('Some moves must be created first')) - move = self.env['account.move'].create(record.move_vals()) + raise UserError(_("Some moves must be created first")) + move = self.env["account.move"].create(record.move_vals()) move.post() res.append(move.id) return res @@ -353,11 +351,10 @@ class AccountLoanLine(models.Model): for record in self: if not record.invoice_ids: if record.loan_id.line_ids.filtered( - lambda r: r.date < record.date and not r.invoice_ids + lambda r: r.date < record.date and not r.invoice_ids ): - raise UserError(_('Some invoices must be created first')) - invoice = self.env['account.invoice'].create( - record.invoice_vals()) + raise UserError(_("Some invoices must be created first")) + invoice = self.env["account.invoice"].create(record.invoice_vals()) res.append(invoice.id) for line in invoice.invoice_line_ids: line._set_taxes() @@ -387,34 +384,31 @@ class AccountLoanLine(models.Model): @api.multi def view_account_moves(self): self.ensure_one() - action = self.env.ref('account.action_move_line_form') + action = self.env.ref("account.action_move_line_form") result = action.read()[0] - result['context'] = { - 'default_loan_line_id': self.id, - 'default_loan_id': self.loan_id.id + result["context"] = { + "default_loan_line_id": self.id, + "default_loan_id": self.loan_id.id, } - result['domain'] = [('loan_line_id', '=', self.id)] + result["domain"] = [("loan_line_id", "=", self.id)] if len(self.move_ids) == 1: - res = self.env.ref('account.move.form', False) - result['views'] = [(res and res.id or False, 'form')] - result['res_id'] = self.move_ids.id + res = self.env.ref("account.move.form", False) + result["views"] = [(res and res.id or False, "form")] + result["res_id"] = self.move_ids.id return result @api.multi def view_account_invoices(self): self.ensure_one() - action = self.env.ref('account.action_invoice_tree2') + action = self.env.ref("account.action_invoice_tree2") result = action.read()[0] - result['context'] = { - 'default_loan_line_id': self.id, - 'default_loan_id': self.loan_id.id + result["context"] = { + "default_loan_line_id": self.id, + "default_loan_id": self.loan_id.id, } - result['domain'] = [ - ('loan_line_id', '=', self.id), - ('type', '=', 'in_invoice') - ] + result["domain"] = [("loan_line_id", "=", self.id), ("type", "=", "in_invoice")] if len(self.invoice_ids) == 1: - res = self.env.ref('account.invoice.supplier.form', False) - result['views'] = [(res and res.id or False, 'form')] - result['res_id'] = self.invoice_ids.id + res = self.env.ref("account.invoice.supplier.form", False) + result["views"] = [(res and res.id or False, "form")] + result["res_id"] = self.invoice_ids.id return result diff --git a/account_loan/model/account_move.py b/account_loan/model/account_move.py index 82a1e357c..2cbb6e766 100644 --- a/account_loan/model/account_move.py +++ b/account_loan/model/account_move.py @@ -5,27 +5,20 @@ from odoo import api, fields, models class AccountMove(models.Model): - _inherit = 'account.move' + _inherit = "account.move" loan_line_id = fields.Many2one( - 'account.loan.line', - readonly=True, - ondelete='restrict', + "account.loan.line", readonly=True, ondelete="restrict", ) loan_id = fields.Many2one( - 'account.loan', - readonly=True, - store=True, - ondelete='restrict', + "account.loan", readonly=True, store=True, ondelete="restrict", ) @api.multi def post(self, invoice=False): res = super().post(invoice=invoice) for record in self: - loan_line_id = record.loan_line_id or ( - invoice and invoice.loan_line_id - ) + loan_line_id = record.loan_line_id or (invoice and invoice.loan_line_id) if loan_line_id: if not record.loan_line_id: record.loan_line_id = loan_line_id diff --git a/account_loan/security/account_loan_security.xml b/account_loan/security/account_loan_security.xml index 2a63fb02d..edb768179 100644 --- a/account_loan/security/account_loan_security.xml +++ b/account_loan/security/account_loan_security.xml @@ -1,12 +1,11 @@ - + Account loan multi-company - - + + ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] - diff --git a/account_loan/tests/test_loan.py b/account_loan/tests/test_loan.py index b74e3d86c..0671c9ad1 100644 --- a/account_loan/tests/test_loan.py +++ b/account_loan/tests/test_loan.py @@ -1,13 +1,14 @@ # Copyright 2018 Creu Blanca # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import logging + +from dateutil.relativedelta import relativedelta + from odoo import fields from odoo.exceptions import UserError from odoo.tests import TransactionCase -from dateutil.relativedelta import relativedelta -import logging - _logger = logging.getLogger(__name__) try: import numpy @@ -18,67 +19,60 @@ except (ImportError, IOError) as err: class TestLoan(TransactionCase): def setUp(self): super().setUp() - self.company = self.browse_ref('base.main_company') - self.company_02 = self.env['res.company'].create({ - 'name': 'Auxiliar company' - }) - self.journal = self.env['account.journal'].create({ - 'company_id': self.company.id, - 'type': 'purchase', - 'name': 'Debts', - 'code': 'DBT', - }) + self.company = self.browse_ref("base.main_company") + self.company_02 = self.env["res.company"].create({"name": "Auxiliar company"}) + self.journal = self.env["account.journal"].create( + { + "company_id": self.company.id, + "type": "purchase", + "name": "Debts", + "code": "DBT", + } + ) self.loan_account = self.create_account( - 'DEP', - 'depreciation', - self.browse_ref('account.data_account_type_current_liabilities').id + "DEP", + "depreciation", + self.browse_ref("account.data_account_type_current_liabilities").id, ) self.payable_account = self.create_account( - 'PAY', - 'payable', - self.browse_ref('account.data_account_type_payable').id + "PAY", "payable", self.browse_ref("account.data_account_type_payable").id ) self.asset_account = self.create_account( - 'ASSET', - 'asset', - self.browse_ref('account.data_account_type_payable').id + "ASSET", "asset", self.browse_ref("account.data_account_type_payable").id ) self.interests_account = self.create_account( - 'FEE', - 'Fees', - self.browse_ref('account.data_account_type_expenses').id) + "FEE", "Fees", self.browse_ref("account.data_account_type_expenses").id + ) self.lt_loan_account = self.create_account( - 'LTD', - 'Long term depreciation', - self.browse_ref( - 'account.data_account_type_non_current_liabilities').id) - self.partner = self.env['res.partner'].create({ - 'name': 'Bank' - }) - self.product = self.env['product.product'].create({ - 'name': 'Payment', - 'type': 'service' - }) - self.interests_product = self.env['product.product'].create({ - 'name': 'Bank fee', - 'type': 'service' - }) + "LTD", + "Long term depreciation", + self.browse_ref("account.data_account_type_non_current_liabilities").id, + ) + self.partner = self.env["res.partner"].create({"name": "Bank"}) + self.product = self.env["product.product"].create( + {"name": "Payment", "type": "service"} + ) + self.interests_product = self.env["product.product"].create( + {"name": "Bank fee", "type": "service"} + ) def test_onchange(self): - loan = self.env['account.loan'].new({ - 'name': 'LOAN', - 'company_id': self.company.id, - 'journal_id': self.journal.id, - 'loan_type': 'fixed-annuity', - 'loan_amount': 100, - 'rate': 1, - 'periods': 2, - 'short_term_loan_account_id': self.loan_account.id, - 'interest_expenses_account_id': self.interests_account.id, - 'product_id': self.product.id, - 'interests_product_id': self.interests_product.id, - 'partner_id': self.partner.id, - }) + loan = self.env["account.loan"].new( + { + "name": "LOAN", + "company_id": self.company.id, + "journal_id": self.journal.id, + "loan_type": "fixed-annuity", + "loan_amount": 100, + "rate": 1, + "periods": 2, + "short_term_loan_account_id": self.loan_account.id, + "interest_expenses_account_id": self.interests_account.id, + "product_id": self.product.id, + "interests_product_id": self.interests_product.id, + "partner_id": self.partner.id, + } + ) journal = loan.journal_id.id loan.is_leasing = True loan._onchange_is_leasing() @@ -88,20 +82,18 @@ class TestLoan(TransactionCase): self.assertFalse(loan.interest_expenses_account_id) def test_round_on_end(self): - loan = self.create_loan('fixed-annuity', 500000, 1, 60) + loan = self.create_loan("fixed-annuity", 500000, 1, 60) loan.round_on_end = True loan.compute_lines() line_1 = loan.line_ids.filtered(lambda r: r.sequence == 1) for line in loan.line_ids: - self.assertAlmostEqual( - line_1.payment_amount, line.payment_amount, 2) - loan.loan_type = 'fixed-principal' + self.assertAlmostEqual(line_1.payment_amount, line.payment_amount, 2) + loan.loan_type = "fixed-principal" loan.compute_lines() line_1 = loan.line_ids.filtered(lambda r: r.sequence == 1) line_end = loan.line_ids.filtered(lambda r: r.sequence == 60) - self.assertNotAlmostEqual( - line_1.payment_amount, line_end.payment_amount, 2) - loan.loan_type = 'interest' + self.assertNotAlmostEqual(line_1.payment_amount, line_end.payment_amount, 2) + loan.loan_type = "interest" loan.compute_lines() line_1 = loan.line_ids.filtered(lambda r: r.sequence == 1) line_end = loan.line_ids.filtered(lambda r: r.sequence == 60) @@ -111,12 +103,13 @@ class TestLoan(TransactionCase): def test_pay_amount_validation(self): amount = 10000 periods = 24 - loan = self.create_loan('fixed-annuity', amount, 1, periods) + loan = self.create_loan("fixed-annuity", amount, 1, periods) self.assertTrue(loan.line_ids) self.assertEqual(len(loan.line_ids), periods) line = loan.line_ids.filtered(lambda r: r.sequence == 1) self.assertAlmostEqual( - - numpy.pmt(1 / 100 / 12, 24, 10000), line.payment_amount, 2) + -numpy.pmt(1 / 100 / 12, 24, 10000), line.payment_amount, 2 + ) self.assertEqual(line.long_term_principal_amount, 0) loan.long_term_loan_account_id = self.lt_loan_account loan.compute_lines() @@ -128,52 +121,45 @@ class TestLoan(TransactionCase): self.assertTrue(line) self.assertFalse(line.move_ids) self.assertFalse(line.invoice_ids) - wzd = self.env['account.loan.generate.wizard'].create({}) + wzd = self.env["account.loan.generate.wizard"].create({}) action = wzd.run() self.assertTrue(action) self.assertFalse(wzd.run()) self.assertTrue(line.move_ids) - self.assertIn(line.move_ids.id, action['domain'][0][2]) + self.assertIn(line.move_ids.id, action["domain"][0][2]) line.move_ids.post() with self.assertRaises(UserError): - self.env['account.loan.pay.amount'].create({ - 'loan_id': loan.id, - 'amount': (amount - amount / periods) / 2, - 'fees': 100, - 'date': line.date + relativedelta(months=-1) - }).run() + self.env["account.loan.pay.amount"].create( + { + "loan_id": loan.id, + "amount": (amount - amount / periods) / 2, + "fees": 100, + "date": line.date + relativedelta(months=-1), + } + ).run() with self.assertRaises(UserError): - self.env['account.loan.pay.amount'].create({ - 'loan_id': loan.id, - 'amount': amount, - 'fees': 100, - 'date': line.date, - }).run() + self.env["account.loan.pay.amount"].create( + {"loan_id": loan.id, "amount": amount, "fees": 100, "date": line.date,} + ).run() with self.assertRaises(UserError): - self.env['account.loan.pay.amount'].create({ - 'loan_id': loan.id, - 'amount': 0, - 'fees': 100, - 'date': line.date, - }).run() + self.env["account.loan.pay.amount"].create( + {"loan_id": loan.id, "amount": 0, "fees": 100, "date": line.date,} + ).run() with self.assertRaises(UserError): - self.env['account.loan.pay.amount'].create({ - 'loan_id': loan.id, - 'amount': -100, - 'fees': 100, - 'date': line.date, - }).run() + self.env["account.loan.pay.amount"].create( + {"loan_id": loan.id, "amount": -100, "fees": 100, "date": line.date,} + ).run() def test_fixed_annuity_begin_loan(self): amount = 10000 periods = 24 - loan = self.create_loan('fixed-annuity-begin', amount, 1, periods) + loan = self.create_loan("fixed-annuity-begin", amount, 1, periods) self.assertTrue(loan.line_ids) self.assertEqual(len(loan.line_ids), periods) line = loan.line_ids.filtered(lambda r: r.sequence == 1) self.assertAlmostEqual( - - numpy.pmt(1 / 100 / 12, 24, 10000, when='begin'), - line.payment_amount, 2) + -numpy.pmt(1 / 100 / 12, 24, 10000, when="begin"), line.payment_amount, 2 + ) self.assertEqual(line.long_term_principal_amount, 0) loan.long_term_loan_account_id = self.lt_loan_account loan.compute_lines() @@ -185,24 +171,28 @@ class TestLoan(TransactionCase): self.assertTrue(line) self.assertFalse(line.move_ids) self.assertFalse(line.invoice_ids) - wzd = self.env['account.loan.generate.wizard'].create({}) + wzd = self.env["account.loan.generate.wizard"].create({}) action = wzd.run() self.assertTrue(action) self.assertFalse(wzd.run()) self.assertTrue(line.move_ids) - self.assertIn(line.move_ids.id, action['domain'][0][2]) + self.assertIn(line.move_ids.id, action["domain"][0][2]) line.move_ids.post() loan.rate = 2 loan.compute_lines() line = loan.line_ids.filtered(lambda r: r.sequence == 1) self.assertAlmostEqual( - - numpy.pmt(1 / 100 / 12, periods, amount, when='begin'), - line.payment_amount, 2) + -numpy.pmt(1 / 100 / 12, periods, amount, when="begin"), + line.payment_amount, + 2, + ) line = loan.line_ids.filtered(lambda r: r.sequence == 2) self.assertAlmostEqual( - - numpy.pmt(2 / 100 / 12, periods - 1, - line.pending_principal_amount, when='begin'), - line.payment_amount, 2 + -numpy.pmt( + 2 / 100 / 12, periods - 1, line.pending_principal_amount, when="begin" + ), + line.payment_amount, + 2, ) line = loan.line_ids.filtered(lambda r: r.sequence == 3) with self.assertRaises(UserError): @@ -211,12 +201,13 @@ class TestLoan(TransactionCase): def test_fixed_annuity_loan(self): amount = 10000 periods = 24 - loan = self.create_loan('fixed-annuity', amount, 1, periods) + loan = self.create_loan("fixed-annuity", amount, 1, periods) self.assertTrue(loan.line_ids) self.assertEqual(len(loan.line_ids), periods) line = loan.line_ids.filtered(lambda r: r.sequence == 1) self.assertAlmostEqual( - - numpy.pmt(1 / 100 / 12, 24, 10000), line.payment_amount, 2) + -numpy.pmt(1 / 100 / 12, 24, 10000), line.payment_amount, 2 + ) self.assertEqual(line.long_term_principal_amount, 0) loan.long_term_loan_account_id = self.lt_loan_account loan.compute_lines() @@ -228,23 +219,24 @@ class TestLoan(TransactionCase): self.assertTrue(line) self.assertFalse(line.move_ids) self.assertFalse(line.invoice_ids) - wzd = self.env['account.loan.generate.wizard'].create({}) + wzd = self.env["account.loan.generate.wizard"].create({}) action = wzd.run() self.assertTrue(action) self.assertFalse(wzd.run()) self.assertTrue(line.move_ids) - self.assertIn(line.move_ids.id, action['domain'][0][2]) + self.assertIn(line.move_ids.id, action["domain"][0][2]) line.move_ids.post() loan.rate = 2 loan.compute_lines() line = loan.line_ids.filtered(lambda r: r.sequence == 1) self.assertAlmostEqual( - - numpy.pmt(1 / 100 / 12, periods, amount), line.payment_amount, 2) + -numpy.pmt(1 / 100 / 12, periods, amount), line.payment_amount, 2 + ) line = loan.line_ids.filtered(lambda r: r.sequence == 2) self.assertAlmostEqual( - - numpy.pmt(2 / 100 / 12, periods - 1, - line.pending_principal_amount), - line.payment_amount, 2 + -numpy.pmt(2 / 100 / 12, periods - 1, line.pending_principal_amount), + line.payment_amount, + 2, ) line = loan.line_ids.filtered(lambda r: r.sequence == 3) with self.assertRaises(UserError): @@ -253,14 +245,14 @@ class TestLoan(TransactionCase): def test_fixed_principal_loan(self): amount = 24000 periods = 24 - loan = self.create_loan('fixed-principal', amount, 1, periods) + loan = self.create_loan("fixed-principal", amount, 1, periods) self.partner.property_account_payable_id = self.payable_account - self.assertEqual(loan.journal_type, 'general') + self.assertEqual(loan.journal_type, "general") loan.is_leasing = True loan.post_invoice = False - self.assertEqual(loan.journal_type, 'purchase') + self.assertEqual(loan.journal_type, "purchase") loan.long_term_loan_account_id = self.lt_loan_account - loan.rate_type = 'real' + loan.rate_type = "real" loan.compute_lines() self.assertTrue(loan.line_ids) self.assertEqual(len(loan.line_ids), periods) @@ -272,60 +264,69 @@ class TestLoan(TransactionCase): self.assertTrue(line) self.assertFalse(line.has_invoices) self.assertFalse(line.has_moves) - action = self.env['account.loan.generate.wizard'].create({ - 'date': fields.date.today(), - 'loan_type': 'leasing', - }).run() + action = ( + self.env["account.loan.generate.wizard"] + .create({"date": fields.date.today(), "loan_type": "leasing",}) + .run() + ) self.assertTrue(line.has_invoices) self.assertFalse(line.has_moves) - self.assertIn(line.invoice_ids.id, action['domain'][0][2]) + self.assertIn(line.invoice_ids.id, action["domain"][0][2]) with self.assertRaises(UserError): - self.env['account.loan.pay.amount'].create({ - 'loan_id': loan.id, - 'amount': (amount - amount / periods) / 2, - 'fees': 100, - 'date': loan.line_ids.filtered( - lambda r: r.sequence == 2).date - }).run() + self.env["account.loan.pay.amount"].create( + { + "loan_id": loan.id, + "amount": (amount - amount / periods) / 2, + "fees": 100, + "date": loan.line_ids.filtered(lambda r: r.sequence == 2).date, + } + ).run() with self.assertRaises(UserError): - self.env['account.loan.pay.amount'].create({ - 'loan_id': loan.id, - 'amount': (amount - amount / periods) / 2, - 'fees': 100, - 'date': loan.line_ids.filtered( - lambda r: r.sequence == 1 - ).date + relativedelta(months=-1) - }).run() + self.env["account.loan.pay.amount"].create( + { + "loan_id": loan.id, + "amount": (amount - amount / periods) / 2, + "fees": 100, + "date": loan.line_ids.filtered(lambda r: r.sequence == 1).date + + relativedelta(months=-1), + } + ).run() line.invoice_ids.action_invoice_open() self.assertTrue(line.has_moves) self.assertIn( line.move_ids.id, - self.env['account.move'].search( - loan.view_account_moves()['domain']).ids + self.env["account.move"].search(loan.view_account_moves()["domain"]).ids, ) self.assertEqual( line.invoice_ids.id, - self.env['account.invoice'].search( - loan.view_account_invoices()['domain']).id + self.env["account.invoice"] + .search(loan.view_account_invoices()["domain"]) + .id, ) with self.assertRaises(UserError): - self.env['account.loan.pay.amount'].create({ - 'loan_id': loan.id, - 'amount': (amount - amount / periods) / 2, - 'fees': 100, - 'date': loan.line_ids.filtered( - lambda r: r.sequence == periods).date - }).run() - self.env['account.loan.pay.amount'].create({ - 'loan_id': loan.id, - 'amount': (amount - amount / periods) / 2, - 'date': line.date, - 'fees': 100, - }).run() + self.env["account.loan.pay.amount"].create( + { + "loan_id": loan.id, + "amount": (amount - amount / periods) / 2, + "fees": 100, + "date": loan.line_ids.filtered( + lambda r: r.sequence == periods + ).date, + } + ).run() + self.env["account.loan.pay.amount"].create( + { + "loan_id": loan.id, + "amount": (amount - amount / periods) / 2, + "date": line.date, + "fees": 100, + } + ).run() line = loan.line_ids.filtered(lambda r: r.sequence == 2) self.assertEqual(loan.periods, periods + 1) self.assertAlmostEqual( - line.principal_amount, (amount - amount / periods) / 2, 2) + line.principal_amount, (amount - amount / periods) / 2, 2 + ) line = loan.line_ids.filtered(lambda r: r.sequence == 3) self.assertEqual(amount / periods / 2, line.principal_amount) line = loan.line_ids.filtered(lambda r: r.sequence == 4) @@ -335,13 +336,13 @@ class TestLoan(TransactionCase): def test_fixed_principal_loan_auto_post(self): amount = 24000 periods = 24 - loan = self.create_loan('fixed-principal', amount, 1, periods) + loan = self.create_loan("fixed-principal", amount, 1, periods) self.partner.property_account_payable_id = self.payable_account - self.assertEqual(loan.journal_type, 'general') + self.assertEqual(loan.journal_type, "general") loan.is_leasing = True - self.assertEqual(loan.journal_type, 'purchase') + self.assertEqual(loan.journal_type, "purchase") loan.long_term_loan_account_id = self.lt_loan_account - loan.rate_type = 'real' + loan.rate_type = "real" loan.compute_lines() self.assertTrue(loan.line_ids) self.assertEqual(len(loan.line_ids), periods) @@ -353,38 +354,37 @@ class TestLoan(TransactionCase): self.assertTrue(line) self.assertFalse(line.has_invoices) self.assertFalse(line.has_moves) - self.env['account.loan.generate.wizard'].create({ - 'date': fields.date.today(), - 'loan_type': 'leasing', - }).run() + self.env["account.loan.generate.wizard"].create( + {"date": fields.date.today(), "loan_type": "leasing",} + ).run() self.assertTrue(line.has_invoices) self.assertTrue(line.has_moves) def test_interests_on_end_loan(self): amount = 10000 periods = 10 - loan = self.create_loan('interest', amount, 1, periods) + loan = self.create_loan("interest", amount, 1, periods) loan.payment_on_first_period = False loan.start_date = fields.Date.today() - loan.rate_type = 'ear' + loan.rate_type = "ear" loan.compute_lines() self.assertTrue(loan.line_ids) self.assertEqual(len(loan.line_ids), periods) self.assertEqual(0, loan.line_ids[0].principal_amount) - self.assertEqual(amount, loan.line_ids.filtered( - lambda r: r.sequence == periods - ).principal_amount) + self.assertEqual( + amount, + loan.line_ids.filtered(lambda r: r.sequence == periods).principal_amount, + ) self.post(loan) self.assertEqual(loan.payment_amount, 0) self.assertEqual(loan.interests_amount, 0) self.assertEqual(loan.pending_principal_amount, amount) - self.assertFalse(loan.line_ids.filtered( - lambda r: r.date <= loan.start_date)) + self.assertFalse(loan.line_ids.filtered(lambda r: r.date <= loan.start_date)) for line in loan.line_ids: - self.assertEqual(loan.state, 'posted') + self.assertEqual(loan.state, "posted") line.view_process_values() line.move_ids.post() - self.assertEqual(loan.state, 'closed') + self.assertEqual(loan.state, "closed") self.assertEqual(loan.payment_amount - loan.interests_amount, amount) self.assertEqual(loan.pending_principal_amount, 0) @@ -392,57 +392,60 @@ class TestLoan(TransactionCase): def test_cancel_loan(self): amount = 10000 periods = 10 - loan = self.create_loan('fixed-annuity', amount, 1, periods) + loan = self.create_loan("fixed-annuity", amount, 1, periods) self.post(loan) line = loan.line_ids.filtered(lambda r: r.sequence == 1) line.view_process_values() line.move_ids.post() - pay = self.env['account.loan.pay.amount'].create({ - 'loan_id': loan.id, - 'amount': 0, - 'fees': 100, - 'date': line.date - }) + pay = self.env["account.loan.pay.amount"].create( + {"loan_id": loan.id, "amount": 0, "fees": 100, "date": line.date} + ) pay.cancel_loan = True pay._onchange_cancel_loan() self.assertEqual(pay.amount, line.final_pending_principal_amount) pay.run() - self.assertEqual(loan.state, 'cancelled') + self.assertEqual(loan.state, "cancelled") def post(self, loan): self.assertFalse(loan.move_ids) - post = self.env['account.loan.post'].with_context( - default_loan_id=loan.id - ).create({}) + post = ( + self.env["account.loan.post"] + .with_context(default_loan_id=loan.id) + .create({}) + ) post.run() self.assertTrue(loan.move_ids) with self.assertRaises(UserError): post.run() def create_account(self, code, name, type_id): - return self.env['account.account'].create({ - 'company_id': self.company.id, - 'name': name, - 'code': code, - 'user_type_id': type_id, - 'reconcile': True, - }) + return self.env["account.account"].create( + { + "company_id": self.company.id, + "name": name, + "code": code, + "user_type_id": type_id, + "reconcile": True, + } + ) def create_loan(self, type_loan, amount, rate, periods): - loan = self.env['account.loan'].create({ - 'journal_id': self.journal.id, - 'rate_type': 'napr', - 'loan_type': type_loan, - 'loan_amount': amount, - 'payment_on_first_period': True, - 'rate': rate, - 'periods': periods, - 'leased_asset_account_id': self.asset_account.id, - 'short_term_loan_account_id': self.loan_account.id, - 'interest_expenses_account_id': self.interests_account.id, - 'product_id': self.product.id, - 'interests_product_id': self.interests_product.id, - 'partner_id': self.partner.id, - }) + loan = self.env["account.loan"].create( + { + "journal_id": self.journal.id, + "rate_type": "napr", + "loan_type": type_loan, + "loan_amount": amount, + "payment_on_first_period": True, + "rate": rate, + "periods": periods, + "leased_asset_account_id": self.asset_account.id, + "short_term_loan_account_id": self.loan_account.id, + "interest_expenses_account_id": self.interests_account.id, + "product_id": self.product.id, + "interests_product_id": self.interests_product.id, + "partner_id": self.partner.id, + } + ) loan.compute_lines() return loan diff --git a/account_loan/views/account_loan_view.xml b/account_loan/views/account_loan_view.xml index 90ea9343a..8ff0758f4 100644 --- a/account_loan/views/account_loan_view.xml +++ b/account_loan/views/account_loan_view.xml @@ -1,202 +1,226 @@ - + - account.loan.tree account.loan - - - - + + + + - account.loan.form account.loan
-
-

- +

- - - + + + - - - + + + - - - - + + + + - - - + + + - - - + + + - + - - - + + + - - - + + + - + - - + + - - - + + +
- - - + + +
- account.loan.line.tree account.loan.line - - - - - - - - - - - - - - - -