commit
5b0a3de8ac
Binary file not shown.
|
|
@ -0,0 +1,64 @@
|
|||
import csv
|
||||
import io
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import fields
|
||||
|
||||
from . import models
|
||||
from . import wizard
|
||||
|
||||
|
||||
def _account_loans_post_init(env):
|
||||
# If in demo, import the demo amortization schedule in the loan
|
||||
if env.ref('base.module_account_loans').demo:
|
||||
_account_loans_import_loan_demo(
|
||||
env,
|
||||
env.ref('account_loans.account_loans_loan_demo1'),
|
||||
env.ref('account_loans.account_loans_loan_demo_file_csv')
|
||||
)
|
||||
|
||||
_account_loans_import_loan_demo(
|
||||
env,
|
||||
env.ref('account_loans.account_loans_loan_demo2'),
|
||||
env.ref('account_loans.account_loans_loan_demo_file_xlsx')
|
||||
)
|
||||
|
||||
|
||||
def _account_loans_add_date_column(csv_attachment):
|
||||
# Modify the CSV such that one part of the loan lines are in the past (so their related
|
||||
# generated entries are posted), and the other part in the future (so in draft)
|
||||
data = io.StringIO()
|
||||
writer = csv.writer(data, delimiter=',')
|
||||
reader = csv.reader(io.StringIO(csv_attachment.raw.decode()), quotechar='"', delimiter=',')
|
||||
current_date = fields.Date.today() - relativedelta(years=1)
|
||||
for i, row in enumerate(reader):
|
||||
if i == 0:
|
||||
row.insert(0, 'Date')
|
||||
else:
|
||||
row.insert(0, current_date.strftime('%Y-%m-%d'))
|
||||
current_date += relativedelta(months=1)
|
||||
writer.writerow(row)
|
||||
data.seek(0) # move offset back to beginning
|
||||
generated_file = data.read()
|
||||
data.close()
|
||||
csv_attachment.raw = generated_file.encode()
|
||||
return csv_attachment
|
||||
|
||||
|
||||
def _account_loans_import_loan_demo(env, loan, attachment):
|
||||
if attachment.mimetype == 'text/csv':
|
||||
attachment = _account_loans_add_date_column(attachment)
|
||||
action = loan.action_upload_amortization_schedule(attachment.id)
|
||||
import_wizard = env['base_import.import'].browse(action.get('params', {}).get('context', {}).get('wizard_id'))
|
||||
result = import_wizard.parse_preview({
|
||||
'quoting': '"',
|
||||
'separator': ',',
|
||||
'date_format': '%Y-%m-%d',
|
||||
'has_headers': True,
|
||||
})
|
||||
import_wizard.with_context(default_loan_id=loan.id).execute_import(
|
||||
['date', 'principal', 'interest'],
|
||||
[],
|
||||
result["options"],
|
||||
)
|
||||
loan.action_file_uploaded()
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
'name': 'Loans Management',
|
||||
'description': """
|
||||
Loans management
|
||||
=================
|
||||
Keeps track of loans, and creates corresponding journal entries.
|
||||
""",
|
||||
'category': 'Accounting/Accounting',
|
||||
'sequence': 32,
|
||||
'depends': ['odex30_account_asset', 'base_import'],
|
||||
'data': [
|
||||
'security/account_loans_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
|
||||
'wizard/account_loan_close_wizard.xml',
|
||||
'wizard/account_loan_compute_wizard.xml',
|
||||
|
||||
'views/account_asset_views.xml',
|
||||
'views/account_asset_group_views.xml',
|
||||
'views/account_loan_views.xml',
|
||||
'views/account_move_views.xml',
|
||||
],
|
||||
'demo': [
|
||||
'demo/account_loans_demo.xml',
|
||||
],
|
||||
'license': 'OEEL-1',
|
||||
'auto_install': True,
|
||||
'post_init_hook': '_account_loans_post_init',
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'account_loans/static/src/**/*',
|
||||
],
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="account_loans_loan_demo_file_csv" model="ir.attachment">
|
||||
<field name="name">loan_amortization_demo.csv</field>
|
||||
<field name="datas" type="base64" file="account_loans/demo/files/loan_amortization_demo.csv"/>
|
||||
</record>
|
||||
|
||||
<record id="account_loans_loan_demo_file_xlsx" model="ir.attachment">
|
||||
<field name="name">loan_amortization_demo.xlsx</field>
|
||||
<field name="datas" type="base64" file="account_loans/demo/files/loan_amortization_demo.xlsx"/>
|
||||
</record>
|
||||
|
||||
<record id="account_loans_journal_loan" model="account.journal">
|
||||
<field name="name">Journal Loan Demo</field>
|
||||
<field name="type">general</field>
|
||||
<field name="code">LOAN</field>
|
||||
</record>
|
||||
|
||||
<record id="account_loans_loan_demo1" model="account.loan">
|
||||
<!-- Used in the __init__.py -> _account_loans_post_init-->
|
||||
<field name="name">Loan Demo 1</field>
|
||||
<field name="asset_group_id" ref="odex30_account_asset.account_asset_group_demo"/>
|
||||
<field name="journal_id" ref="account_loans.account_loans_journal_loan"/>
|
||||
<field name="long_term_account_id" model="account.account" search="[
|
||||
('company_ids', '=', ref('base.main_company')),
|
||||
('account_type', '=', 'liability_non_current'),
|
||||
]"/>
|
||||
<field name="short_term_account_id" model="account.account" search="[
|
||||
('company_ids', '=', ref('base.main_company')),
|
||||
('account_type', '=', 'liability_current'),
|
||||
]"/>
|
||||
<field name="expense_account_id" model="account.account" search="[
|
||||
('company_ids', '=', ref('base.main_company')),
|
||||
('account_type', '=', 'expense'),
|
||||
('id', '!=', obj().env.user.company_id.account_journal_early_pay_discount_loss_account_id.id),
|
||||
]"/>
|
||||
</record>
|
||||
|
||||
<record id="account_loans_loan_demo2" model="account.loan">
|
||||
<!-- Used in the __init__.py -> _account_loans_post_init-->
|
||||
<field name="name">Loan Demo 2</field>
|
||||
<field name="asset_group_id" ref="odex30_account_asset.account_asset_group_demo"/>
|
||||
<field name="journal_id" ref="account_loans.account_loans_journal_loan"/>
|
||||
<field name="long_term_account_id" model="account.account" search="[
|
||||
('company_ids', '=', ref('base.main_company')),
|
||||
('account_type', '=', 'liability_non_current'),
|
||||
]"/>
|
||||
<field name="short_term_account_id" model="account.account" search="[
|
||||
('company_ids', '=', ref('base.main_company')),
|
||||
('account_type', '=', 'liability_current'),
|
||||
]"/>
|
||||
<field name="expense_account_id" model="account.account" search="[
|
||||
('company_ids', '=', ref('base.main_company')),
|
||||
('account_type', '=', 'expense'),
|
||||
('id', '!=', obj().env.user.company_id.account_journal_early_pay_discount_loss_account_id.id),
|
||||
]"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
Principal,Interest
|
||||
304.60,250.00
|
||||
308.42,246.17
|
||||
312.30,242.30
|
||||
316.22,238.38
|
||||
320.19,234.40
|
||||
324.22,230.38
|
||||
328.29,226.31
|
||||
332.41,222.18
|
||||
336.59,218.01
|
||||
340.82,213.78
|
||||
345.10,209.50
|
||||
349.44,205.16
|
||||
353.83,200.77
|
||||
358.27,196.33
|
||||
362.77,191.83
|
||||
367.33,187.27
|
||||
371.94,182.65
|
||||
376.62,177.98
|
||||
381.35,173.25
|
||||
386.14,168.46
|
||||
390.99,163.61
|
||||
395.90,158.70
|
||||
400.87,153.72
|
||||
405.91,148.69
|
||||
411.01,143.59
|
||||
416.17,138.42
|
||||
421.40,133.20
|
||||
426.70,127.90
|
||||
432.06,122.54
|
||||
437.48,117.11
|
||||
442.98,111.62
|
||||
448.54,106.05
|
||||
454.18,100.42
|
||||
459.89,94.71
|
||||
465.66,88.94
|
||||
471.51,83.09
|
||||
477.44,77.16
|
||||
483.43,71.16
|
||||
489.51,65.09
|
||||
495.66,58.94
|
||||
501.88,52.71
|
||||
508.19,46.41
|
||||
514.57,40.03
|
||||
521.04,33.56
|
||||
527.58,27.02
|
||||
534.21,20.39
|
||||
540.92,13.68
|
||||
547.72,6.88
|
||||
|
Binary file not shown.
|
|
@ -0,0 +1,961 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * account_loans
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 18.0+e\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-03-26 20:45+0000\n"
|
||||
"PO-Revision-Date: 2025-03-26 20:45+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "1st amortization schedule"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan_compute_wizard__compounding_method__30a/360
|
||||
msgid "30A/360"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan_compute_wizard__compounding_method__30e/360
|
||||
msgid "30E/360"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan_compute_wizard__compounding_method__30e/360_isda
|
||||
msgid "30E/360 ISDA"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan_compute_wizard__compounding_method__30u/360
|
||||
msgid "30U/360"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid ""
|
||||
"<span class=\"oe_inline\" invisible=\"duration == 1\">months</span>\n"
|
||||
" <span class=\"oe_inline\" invisible=\"duration != 1\">month</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_compute_wizard
|
||||
msgid ""
|
||||
"<span class=\"oe_inline\" invisible=\"loan_term == 1\">years</span>\n"
|
||||
" <span class=\"oe_inline\" invisible=\"loan_term != 1\">year</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_compute_wizard
|
||||
msgid "<span class=\"oe_inline\">%</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan_compute_wizard__compounding_method__a/360
|
||||
msgid "A/360"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan_compute_wizard__compounding_method__a/365f
|
||||
msgid "A/365F"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan_compute_wizard__compounding_method__a/a_afb
|
||||
msgid "A/A AFB"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan_compute_wizard__compounding_method__a/a_isda
|
||||
msgid "A/A ISDA"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__message_needaction
|
||||
msgid "Action Needed"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__active
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__active
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_close_wizard
|
||||
msgid "All draft entries after the"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Amortization schedule"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__amount_borrowed
|
||||
msgid "Amount Borrowed"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__amount_borrowed_difference
|
||||
msgid "Amount Borrowed Difference"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_close_wizard
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_compute_wizard
|
||||
msgid "Apply"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model,name:account_loans.model_account_asset_group
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__asset_group_id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__loan_asset_group_id
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_search_view
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_search_view
|
||||
msgid "Asset Group"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model,name:account_loans.model_account_asset
|
||||
msgid "Asset/Revenue Recognition"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan_compute_wizard__payment_end_of_month__at_anniversary
|
||||
msgid "At Anniversary"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__message_attachment_count
|
||||
msgid "Attachment Count"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/wizard/account_loan_compute_wizard.py:0
|
||||
msgid "Balance"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan__state__cancelled
|
||||
msgid "Cancelled"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_close_wizard__date
|
||||
msgid "Close Date"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.actions.act_window,name:account_loans.action_view_account_loan_close_wizard
|
||||
#: model:ir.model,name:account_loans.model_account_loan_close_wizard
|
||||
msgid "Close Loan Wizard"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan__state__closed
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_search_view
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_search_view
|
||||
msgid "Closed"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_search_view
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_search_view
|
||||
msgid "Closed Loans"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/wizard/account_loan_close_wizard.py:0
|
||||
msgid "Closed on the %(date)s"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__company_id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__company_id
|
||||
msgid "Company"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__compounding_method
|
||||
msgid "Compounding Method"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-javascript
|
||||
#: code:addons/account_loans/static/src/components/loans/file_upload.xml:0
|
||||
msgid "Compute"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "Compute New Loan"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__count_linked_assets
|
||||
msgid "Count Linked Assets"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_asset__count_linked_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_asset_group__count_linked_loans
|
||||
msgid "Count Linked Loans"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__create_uid
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_close_wizard__create_uid
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__create_uid
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__create_uid
|
||||
msgid "Created by"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__create_date
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_close_wizard__create_date
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__create_date
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__create_date
|
||||
msgid "Created on"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__currency_id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__currency_id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__currency_id
|
||||
msgid "Currency"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_search_view
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_search_view
|
||||
msgid "Current"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/wizard/account_loan_compute_wizard.py:0
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__date
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_search_view
|
||||
msgid "Date"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_close_wizard
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_compute_wizard
|
||||
msgid "Discard"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_close_wizard__display_name
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__display_name
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__display_name
|
||||
msgid "Display Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan__state__draft
|
||||
msgid "Draft"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_search_view
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_search_view
|
||||
msgid "Draft & Running Loans"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "Due"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__duration
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Duration"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__duration_difference
|
||||
msgid "Duration Difference"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__end_date
|
||||
msgid "End Date"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan_compute_wizard__payment_end_of_month__end_of_month
|
||||
msgid "End of Month"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,help:account_loans.field_account_loan_line__generated_move_ids
|
||||
msgid "Entries that we generated from this loan line"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__expense_account_id
|
||||
msgid "Expense Account"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__first_payment_date
|
||||
msgid "First Payment"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__message_follower_ids
|
||||
msgid "Followers"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__message_partner_ids
|
||||
msgid "Followers (Partners)"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__generated_move_ids
|
||||
msgid "Generated Entries"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_bank_statement_line__generating_loan_line_id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_move__generating_loan_line_id
|
||||
msgid "Generating Loan Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_search_view
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_search_view
|
||||
msgid "Group By"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__has_message
|
||||
msgid "Has Message"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_close_wizard__id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__id
|
||||
msgid "ID"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,help:account_loans.field_account_loan__message_needaction
|
||||
msgid "If checked, new messages require your attention."
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,help:account_loans.field_account_loan__message_has_error
|
||||
#: model:ir.model.fields,help:account_loans.field_account_loan__message_has_sms_error
|
||||
msgid "If checked, some messages have a delivery error."
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
#: code:addons/account_loans/wizard/account_loan_compute_wizard.py:0
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__interest
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__interest
|
||||
msgid "Interest"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__interest_difference
|
||||
msgid "Interest Difference"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__interest_rate
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_compute_wizard
|
||||
msgid "Interest Rate"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/wizard/account_loan_compute_wizard.py:0
|
||||
msgid "Interest Rate must be between 0 and 100"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_list_view
|
||||
msgid "Interests"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__message_is_follower
|
||||
msgid "Is Follower"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_bank_statement_line__is_loan_payment_move
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_move__is_loan_payment_move
|
||||
msgid "Is Loan Payment Move"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__is_payment_move_posted
|
||||
msgid "Is Payment Move Posted"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__is_wrong_date
|
||||
msgid "Is Wrong Date"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model,name:account_loans.model_account_journal
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__journal_id
|
||||
msgid "Journal"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model,name:account_loans.model_account_move
|
||||
msgid "Journal Entry"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:account.journal,name:account_loans.account_loans_journal_loan
|
||||
msgid "Journal Loan Demo"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__write_uid
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_close_wizard__write_uid
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__write_uid
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__write_uid
|
||||
msgid "Last Updated by"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__write_date
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_close_wizard__write_date
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__write_date
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__write_date
|
||||
msgid "Last Updated on"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,help:account_loans.field_account_bank_statement_line__generating_loan_line_id
|
||||
#: model:ir.model.fields,help:account_loans.field_account_move__generating_loan_line_id
|
||||
msgid "Line of the loan that generated this entry"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__linked_assets_ids
|
||||
msgid "Linked Assets"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_asset.py:0
|
||||
msgid "Linked loans"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model,name:account_loans.model_account_loan
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_bank_statement_line__loan_id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_close_wizard__loan_id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__loan_id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__loan_id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_move__loan_id
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_search_view
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_search_view
|
||||
msgid "Loan"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__loan_amount
|
||||
msgid "Loan Amount"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/wizard/account_loan_compute_wizard.py:0
|
||||
msgid "Loan Amount must be positive"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.actions.act_window,name:account_loans.action_view_account_loan_compute_wizard
|
||||
#: model:ir.model,name:account_loans.model_account_loan_compute_wizard
|
||||
msgid "Loan Compute Wizard"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__date
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__loan_date
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_search_view
|
||||
msgid "Loan Date"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "Loan Entries"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model,name:account_loans.model_account_loan_line
|
||||
msgid "Loan Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__line_ids
|
||||
msgid "Loan Lines"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Loan Settings"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__loan_term
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_compute_wizard
|
||||
msgid "Loan Term"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/wizard/account_loan_compute_wizard.py:0
|
||||
msgid "Loan Term must be positive"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_list_view
|
||||
msgid "Loan lines"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__display_name
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Loan name"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.asset_group_form_view_inherit_loan
|
||||
msgid "Loan(s)"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
#: model:ir.actions.act_window,name:account_loans.action_view_account_loans
|
||||
#: model:ir.ui.menu,name:account_loans.menu_action_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_list_view
|
||||
msgid "Loans"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.actions.act_window,name:account_loans.action_view_account_loans_analysis
|
||||
#: model:ir.ui.menu,name:account_loans.menu_action_loans_analysis
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_pivot_view
|
||||
msgid "Loans Analysis"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__long_term_account_id
|
||||
msgid "Long Term Account"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__long_term_theoretical_balance
|
||||
msgid "Long-Term"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.actions.act_window,help:account_loans.action_view_account_loans
|
||||
msgid "Manage Your Acquired Loans with Automated Adjustments."
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__message_has_error
|
||||
msgid "Message Delivery error"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__message_ids
|
||||
msgid "Messages"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_journal__loan_properties_definition
|
||||
msgid "Model Properties"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__name
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__loan_name
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__nb_posted_entries
|
||||
msgid "Nb Posted Entries"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__message_needaction_counter
|
||||
msgid "Number of Actions"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__message_has_error_counter
|
||||
msgid "Number of errors"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,help:account_loans.field_account_loan__message_needaction_counter
|
||||
msgid "Number of messages requiring action"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,help:account_loans.field_account_loan__message_has_error_counter
|
||||
msgid "Number of messages with delivery error"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_move.py:0
|
||||
msgid "Original Loan"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__outstanding_balance
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__outstanding_balance
|
||||
msgid "Outstanding Balance"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/wizard/account_loan_compute_wizard.py:0
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__payment_end_of_month
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__payment
|
||||
msgid "Payment"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_list_view
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "Please add a name before computing the loan"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Posted Entries"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__preview
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_compute_wizard
|
||||
msgid "Preview"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
#: code:addons/account_loans/wizard/account_loan_compute_wizard.py:0
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__principal
|
||||
msgid "Principal"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "Principal & Interest"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_list_view
|
||||
msgid "Principals"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__loan_properties
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_search_view
|
||||
msgid "Properties"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__rating_ids
|
||||
msgid "Ratings"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "Reclassification LT - ST"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Related Asset(s)"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_move_form_inherit_loan
|
||||
msgid "Related Loan"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_asset_form_inherit_loan
|
||||
msgid "Related Loan(s)"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_asset__linked_loans_ids
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_asset_group__linked_loan_ids
|
||||
msgid "Related Loans"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Reset"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "Reversal reclassification LT - ST"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan__state__running
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__message_has_sms_error
|
||||
msgid "SMS Delivery error"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_search_view
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_search_view
|
||||
msgid "Search Loan"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Set to Draft"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.actions.act_window,help:account_loans.action_view_account_loans
|
||||
msgid ""
|
||||
"Set up your amortization schedule, or import it, and let Odoo handle the "
|
||||
"monthly interest and principal adjustments automatically."
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__short_term_account_id
|
||||
msgid "Short Term Account"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__short_term_theoretical_balance
|
||||
msgid "Short-Term"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__skip_until_date
|
||||
msgid "Skip until"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__start_date
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__start_date
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_list_view
|
||||
msgid "Start Date"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__state
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__loan_state
|
||||
msgid "Status"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/wizard/account_loan_compute_wizard.py:0
|
||||
msgid "The First Payment Date must be before the end of the loan."
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "The amount borrowed must be positive"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "The duration must be positive"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "The interest must be positive"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "The loan accounts should be set."
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid ""
|
||||
"The loan amount %(loan_amount)s should be equal to the sum of the "
|
||||
"principals: %(principal_sum)s (difference %(principal_difference)s)"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "The loan date should be earlier than the loan lines date."
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "The loan duration should be equal to the number of loan lines."
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid ""
|
||||
"The loan interest should be equal to the sum of the loan lines interest."
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "The loan journal should be set."
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "The loan name should be set."
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "This entry has been %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "This entry has been reversed from %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_list_view
|
||||
msgid "Total Amounts Borrowed"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_list_view
|
||||
msgid "Total Outstanding Balance"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_list_view
|
||||
msgid "Total interests"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_list_view
|
||||
msgid "Total payments"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_list_view
|
||||
msgid "Total principals"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-javascript
|
||||
#: code:addons/account_loans/static/src/components/loans/file_upload.xml:0
|
||||
msgid "Upload"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "Uploaded file"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,help:account_loans.field_account_loan__skip_until_date
|
||||
msgid ""
|
||||
"Upon confirmation of the loan, Odoo will ignore the loan lines that are up "
|
||||
"to this date (included) and not create entries for them. This is useful if "
|
||||
"you have already manually created entries prior to the creation of this "
|
||||
"loan."
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__website_message_ids
|
||||
msgid "Website Messages"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,help:account_loans.field_account_loan__website_message_ids
|
||||
msgid "Website communication history"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_close_wizard
|
||||
msgid "will be deleted and the loan will be marked as closed."
|
||||
msgstr ""
|
||||
|
|
@ -0,0 +1,977 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * account_loans
|
||||
#
|
||||
# Translators:
|
||||
# Wil Odoo, 2025
|
||||
# Malaz Abuidris <msea@odoo.com>, 2025
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 18.0+e\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-03-26 20:45+0000\n"
|
||||
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
|
||||
"Last-Translator: Malaz Abuidris <msea@odoo.com>, 2025\n"
|
||||
"Language-Team: Arabic (https://app.transifex.com/odoo/teams/41243/ar/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Language: ar\n"
|
||||
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "1st amortization schedule"
|
||||
msgstr "جدول الاستهلاك الأول "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan_compute_wizard__compounding_method__30a/360
|
||||
msgid "30A/360"
|
||||
msgstr "30A/360"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan_compute_wizard__compounding_method__30e/360
|
||||
msgid "30E/360"
|
||||
msgstr "30E/360"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan_compute_wizard__compounding_method__30e/360_isda
|
||||
msgid "30E/360 ISDA"
|
||||
msgstr "30E/360 ISDA"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan_compute_wizard__compounding_method__30u/360
|
||||
msgid "30U/360"
|
||||
msgstr "30U/360"
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid ""
|
||||
"<span class=\"oe_inline\" invisible=\"duration == 1\">months</span>\n"
|
||||
" <span class=\"oe_inline\" invisible=\"duration != 1\">month</span>"
|
||||
msgstr ""
|
||||
"<span class=\"oe_inline\" invisible=\"duration == 1\">شهور</span>\n"
|
||||
" <span class=\"oe_inline\" invisible=\"duration != 1\">شهر</span>"
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_compute_wizard
|
||||
msgid ""
|
||||
"<span class=\"oe_inline\" invisible=\"loan_term == 1\">years</span>\n"
|
||||
" <span class=\"oe_inline\" invisible=\"loan_term != 1\">year</span>"
|
||||
msgstr ""
|
||||
"<span class=\"oe_inline\" invisible=\"loan_term == 1\">سنوات</span>\n"
|
||||
" <span class=\"oe_inline\" invisible=\"loan_term != 1\">سنة</span> "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_compute_wizard
|
||||
msgid "<span class=\"oe_inline\">%</span>"
|
||||
msgstr "<span class=\"oe_inline\">%</span>"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan_compute_wizard__compounding_method__a/360
|
||||
msgid "A/360"
|
||||
msgstr "A/360"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan_compute_wizard__compounding_method__a/365f
|
||||
msgid "A/365F"
|
||||
msgstr "A/365F"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan_compute_wizard__compounding_method__a/a_afb
|
||||
msgid "A/A AFB"
|
||||
msgstr "A/A AFB"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan_compute_wizard__compounding_method__a/a_isda
|
||||
msgid "A/A ISDA"
|
||||
msgstr "A/A ISDA"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__message_needaction
|
||||
msgid "Action Needed"
|
||||
msgstr "إجراء مطلوب"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__active
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__active
|
||||
msgid "Active"
|
||||
msgstr "نشط"
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_close_wizard
|
||||
msgid "All draft entries after the"
|
||||
msgstr "تمت إزالة موصل eBay. "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Amortization schedule"
|
||||
msgstr "جدول الاستهلاك "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__amount_borrowed
|
||||
msgid "Amount Borrowed"
|
||||
msgstr "المبلغ المقترض "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__amount_borrowed_difference
|
||||
msgid "Amount Borrowed Difference"
|
||||
msgstr "فرق المبلغ المقترض "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_close_wizard
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_compute_wizard
|
||||
msgid "Apply"
|
||||
msgstr "تطبيق"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model,name:account_loans.model_account_asset_group
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__asset_group_id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__loan_asset_group_id
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_search_view
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_search_view
|
||||
msgid "Asset Group"
|
||||
msgstr "مجموعة الأصول "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model,name:account_loans.model_account_asset
|
||||
msgid "Asset/Revenue Recognition"
|
||||
msgstr "إثبات الأصل/الإيرادات "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan_compute_wizard__payment_end_of_month__at_anniversary
|
||||
msgid "At Anniversary"
|
||||
msgstr "في الذكرى السنوية "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__message_attachment_count
|
||||
msgid "Attachment Count"
|
||||
msgstr "عدد المرفقات"
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/wizard/account_loan_compute_wizard.py:0
|
||||
msgid "Balance"
|
||||
msgstr "الرصيد"
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Cancel"
|
||||
msgstr "إلغاء"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan__state__cancelled
|
||||
msgid "Cancelled"
|
||||
msgstr "تم الإلغاء "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Close"
|
||||
msgstr "إغلاق"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_close_wizard__date
|
||||
msgid "Close Date"
|
||||
msgstr "تاريخ الإغلاق"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.actions.act_window,name:account_loans.action_view_account_loan_close_wizard
|
||||
#: model:ir.model,name:account_loans.model_account_loan_close_wizard
|
||||
msgid "Close Loan Wizard"
|
||||
msgstr "معالج إغلاق القروض "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan__state__closed
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_search_view
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_search_view
|
||||
msgid "Closed"
|
||||
msgstr "مغلق"
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_search_view
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_search_view
|
||||
msgid "Closed Loans"
|
||||
msgstr "القروض المغلقة "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/wizard/account_loan_close_wizard.py:0
|
||||
msgid "Closed on the %(date)s"
|
||||
msgstr "تم إغلاقه في %(date)s "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__company_id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__company_id
|
||||
msgid "Company"
|
||||
msgstr "الشركة "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__compounding_method
|
||||
msgid "Compounding Method"
|
||||
msgstr "طريقة التركيب "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-javascript
|
||||
#: code:addons/account_loans/static/src/components/loans/file_upload.xml:0
|
||||
msgid "Compute"
|
||||
msgstr "احتساب "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "Compute New Loan"
|
||||
msgstr "حساب قرض جديد "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Confirm"
|
||||
msgstr "تأكيد"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__count_linked_assets
|
||||
msgid "Count Linked Assets"
|
||||
msgstr "عدّ الأصول المرتبطة "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_asset__count_linked_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_asset_group__count_linked_loans
|
||||
msgid "Count Linked Loans"
|
||||
msgstr "عدّ القروض المرتبطة "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__create_uid
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_close_wizard__create_uid
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__create_uid
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__create_uid
|
||||
msgid "Created by"
|
||||
msgstr "أنشئ بواسطة"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__create_date
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_close_wizard__create_date
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__create_date
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__create_date
|
||||
msgid "Created on"
|
||||
msgstr "أنشئ في"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__currency_id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__currency_id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__currency_id
|
||||
msgid "Currency"
|
||||
msgstr "العملة"
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_search_view
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_search_view
|
||||
msgid "Current"
|
||||
msgstr "الحالي "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/wizard/account_loan_compute_wizard.py:0
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__date
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_search_view
|
||||
msgid "Date"
|
||||
msgstr "التاريخ"
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_close_wizard
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_compute_wizard
|
||||
msgid "Discard"
|
||||
msgstr "إهمال "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_close_wizard__display_name
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__display_name
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__display_name
|
||||
msgid "Display Name"
|
||||
msgstr "اسم العرض "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan__state__draft
|
||||
msgid "Draft"
|
||||
msgstr "مسودة"
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_search_view
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_search_view
|
||||
msgid "Draft & Running Loans"
|
||||
msgstr "مسودات القروض والقروض الجارية "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "Due"
|
||||
msgstr "مستحق"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__duration
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Duration"
|
||||
msgstr "المدة"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__duration_difference
|
||||
msgid "Duration Difference"
|
||||
msgstr "فرق المدة "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__end_date
|
||||
msgid "End Date"
|
||||
msgstr "تاريخ الانتهاء"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan_compute_wizard__payment_end_of_month__end_of_month
|
||||
msgid "End of Month"
|
||||
msgstr "نهاية الشهر "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,help:account_loans.field_account_loan_line__generated_move_ids
|
||||
msgid "Entries that we generated from this loan line"
|
||||
msgstr "الإدخالات التي قمنا بإنشائها من بند القرض هذا "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__expense_account_id
|
||||
msgid "Expense Account"
|
||||
msgstr "حساب النفقات "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__first_payment_date
|
||||
msgid "First Payment"
|
||||
msgstr "الدفعة الأولى "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__message_follower_ids
|
||||
msgid "Followers"
|
||||
msgstr "المتابعين"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__message_partner_ids
|
||||
msgid "Followers (Partners)"
|
||||
msgstr "المتابعين (الشركاء) "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__generated_move_ids
|
||||
msgid "Generated Entries"
|
||||
msgstr "القيود المُنشأة "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_bank_statement_line__generating_loan_line_id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_move__generating_loan_line_id
|
||||
msgid "Generating Loan Line"
|
||||
msgstr "إنشاء بند القرض "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_search_view
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_search_view
|
||||
msgid "Group By"
|
||||
msgstr "التجميع حسب "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__has_message
|
||||
msgid "Has Message"
|
||||
msgstr "يحتوي على رسالة "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_close_wizard__id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__id
|
||||
msgid "ID"
|
||||
msgstr "المُعرف"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,help:account_loans.field_account_loan__message_needaction
|
||||
msgid "If checked, new messages require your attention."
|
||||
msgstr "إذا كان محددًا، فهناك رسائل جديدة عليك رؤيتها. "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,help:account_loans.field_account_loan__message_has_error
|
||||
#: model:ir.model.fields,help:account_loans.field_account_loan__message_has_sms_error
|
||||
msgid "If checked, some messages have a delivery error."
|
||||
msgstr "إذا كان محددًا، فقد حدث خطأ في تسليم بعض الرسائل."
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
#: code:addons/account_loans/wizard/account_loan_compute_wizard.py:0
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__interest
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__interest
|
||||
msgid "Interest"
|
||||
msgstr "الفائدة "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__interest_difference
|
||||
msgid "Interest Difference"
|
||||
msgstr "فرق الفوائد "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__interest_rate
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_compute_wizard
|
||||
msgid "Interest Rate"
|
||||
msgstr "نسبة الفوائد "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/wizard/account_loan_compute_wizard.py:0
|
||||
msgid "Interest Rate must be between 0 and 100"
|
||||
msgstr "يجب أن تكون نسبة الفوائد بين 0 و 100 "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_list_view
|
||||
msgid "Interests"
|
||||
msgstr "الفوائد "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__message_is_follower
|
||||
msgid "Is Follower"
|
||||
msgstr "متابع"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_bank_statement_line__is_loan_payment_move
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_move__is_loan_payment_move
|
||||
msgid "Is Loan Payment Move"
|
||||
msgstr "حركة سداد القرض "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__is_payment_move_posted
|
||||
msgid "Is Payment Move Posted"
|
||||
msgstr "تم ترحيل حركة الدفع "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__is_wrong_date
|
||||
msgid "Is Wrong Date"
|
||||
msgstr "التاريخ غير صحيح "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model,name:account_loans.model_account_journal
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__journal_id
|
||||
msgid "Journal"
|
||||
msgstr "دفتر اليومية"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model,name:account_loans.model_account_move
|
||||
msgid "Journal Entry"
|
||||
msgstr "قيد اليومية"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:account.journal,name:account_loans.account_loans_journal_loan
|
||||
msgid "Journal Loan Demo"
|
||||
msgstr "العرض التوضيحي لقرض في دفتر اليومية "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__write_uid
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_close_wizard__write_uid
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__write_uid
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__write_uid
|
||||
msgid "Last Updated by"
|
||||
msgstr "آخر تحديث بواسطة"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__write_date
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_close_wizard__write_date
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__write_date
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__write_date
|
||||
msgid "Last Updated on"
|
||||
msgstr "آخر تحديث في"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,help:account_loans.field_account_bank_statement_line__generating_loan_line_id
|
||||
#: model:ir.model.fields,help:account_loans.field_account_move__generating_loan_line_id
|
||||
msgid "Line of the loan that generated this entry"
|
||||
msgstr "بند القرض الذي أدى إلى إنشاء هذا القيد "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__linked_assets_ids
|
||||
msgid "Linked Assets"
|
||||
msgstr "الأصول المرتبطة "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_asset.py:0
|
||||
msgid "Linked loans"
|
||||
msgstr "القروض المربطة "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model,name:account_loans.model_account_loan
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_bank_statement_line__loan_id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_close_wizard__loan_id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__loan_id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__loan_id
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_move__loan_id
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_search_view
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_search_view
|
||||
msgid "Loan"
|
||||
msgstr "قرض "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__loan_amount
|
||||
msgid "Loan Amount"
|
||||
msgstr "مبلغ القرض "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/wizard/account_loan_compute_wizard.py:0
|
||||
msgid "Loan Amount must be positive"
|
||||
msgstr "يجب أن يكون مبلغ القرض موجباً "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.actions.act_window,name:account_loans.action_view_account_loan_compute_wizard
|
||||
#: model:ir.model,name:account_loans.model_account_loan_compute_wizard
|
||||
msgid "Loan Compute Wizard"
|
||||
msgstr "معالج حساب القروض "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__date
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__loan_date
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_search_view
|
||||
msgid "Loan Date"
|
||||
msgstr "تاريخ القرض "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "Loan Entries"
|
||||
msgstr "قيود القرض "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model,name:account_loans.model_account_loan_line
|
||||
msgid "Loan Line"
|
||||
msgstr "بند القرض "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__line_ids
|
||||
msgid "Loan Lines"
|
||||
msgstr "بنود القرض "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Loan Settings"
|
||||
msgstr "إعدادات القرض "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__loan_term
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_compute_wizard
|
||||
msgid "Loan Term"
|
||||
msgstr "مدة القرض "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/wizard/account_loan_compute_wizard.py:0
|
||||
msgid "Loan Term must be positive"
|
||||
msgstr "يجب أن تكون مدة القرض قيمة موجبة "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_list_view
|
||||
msgid "Loan lines"
|
||||
msgstr "بنود القرض "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__display_name
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Loan name"
|
||||
msgstr "اسم القرض "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.asset_group_form_view_inherit_loan
|
||||
msgid "Loan(s)"
|
||||
msgstr "القروض "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
#: model:ir.actions.act_window,name:account_loans.action_view_account_loans
|
||||
#: model:ir.ui.menu,name:account_loans.menu_action_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_list_view
|
||||
msgid "Loans"
|
||||
msgstr "القروض "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.actions.act_window,name:account_loans.action_view_account_loans_analysis
|
||||
#: model:ir.ui.menu,name:account_loans.menu_action_loans_analysis
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_pivot_view
|
||||
msgid "Loans Analysis"
|
||||
msgstr "تحليل القروض "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__long_term_account_id
|
||||
msgid "Long Term Account"
|
||||
msgstr "حساب طويل الأجل "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__long_term_theoretical_balance
|
||||
msgid "Long-Term"
|
||||
msgstr "طويل لأجل "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.actions.act_window,help:account_loans.action_view_account_loans
|
||||
msgid "Manage Your Acquired Loans with Automated Adjustments."
|
||||
msgstr "قم بإدارة قروضك التي حصلت عليها من خلال التعديلات الآلية. "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__message_has_error
|
||||
msgid "Message Delivery error"
|
||||
msgstr "خطأ في تسليم الرسائل"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__message_ids
|
||||
msgid "Messages"
|
||||
msgstr "الرسائل"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_journal__loan_properties_definition
|
||||
msgid "Model Properties"
|
||||
msgstr "خصائص النموذج "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__name
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__loan_name
|
||||
msgid "Name"
|
||||
msgstr "الاسم"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__nb_posted_entries
|
||||
msgid "Nb Posted Entries"
|
||||
msgstr "عدد القيود المُرحّلَة "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__message_needaction_counter
|
||||
msgid "Number of Actions"
|
||||
msgstr "عدد الإجراءات"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__message_has_error_counter
|
||||
msgid "Number of errors"
|
||||
msgstr "عدد الأخطاء "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,help:account_loans.field_account_loan__message_needaction_counter
|
||||
msgid "Number of messages requiring action"
|
||||
msgstr "عدد الرسائل التي تتطلب اتخاذ إجراء"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,help:account_loans.field_account_loan__message_has_error_counter
|
||||
msgid "Number of messages with delivery error"
|
||||
msgstr "عدد الرسائل الحادث بها خطأ في التسليم"
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_move.py:0
|
||||
msgid "Original Loan"
|
||||
msgstr "القرض الأصلي "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__outstanding_balance
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__outstanding_balance
|
||||
msgid "Outstanding Balance"
|
||||
msgstr "المبلغ المستحق "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/wizard/account_loan_compute_wizard.py:0
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__payment_end_of_month
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__payment
|
||||
msgid "Payment"
|
||||
msgstr "الدفع "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_list_view
|
||||
msgid "Payments"
|
||||
msgstr "المدفوعات"
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "Please add a name before computing the loan"
|
||||
msgstr "يرجى إضافة اسم قبل حساب مبلغ القرض "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Posted Entries"
|
||||
msgstr "القيود المُرحّلة "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__preview
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_compute_wizard
|
||||
msgid "Preview"
|
||||
msgstr "معاينة"
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
#: code:addons/account_loans/wizard/account_loan_compute_wizard.py:0
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__principal
|
||||
msgid "Principal"
|
||||
msgstr "أصل الدَّيْن "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "Principal & Interest"
|
||||
msgstr "أصل الدين والفوائد "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_list_view
|
||||
msgid "Principals"
|
||||
msgstr "أصول الديون "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__loan_properties
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_search_view
|
||||
msgid "Properties"
|
||||
msgstr "الخصائص "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__rating_ids
|
||||
msgid "Ratings"
|
||||
msgstr "التقييمات "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "Reclassification LT - ST"
|
||||
msgstr "إعادة التصنيف LT - ST "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Related Asset(s)"
|
||||
msgstr "الأصول ذات الصلة "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_move_form_inherit_loan
|
||||
msgid "Related Loan"
|
||||
msgstr "القرض ذو الصلة "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_asset_form_inherit_loan
|
||||
msgid "Related Loan(s)"
|
||||
msgstr "القروض ذات الصلة "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_asset__linked_loans_ids
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_asset_group__linked_loan_ids
|
||||
msgid "Related Loans"
|
||||
msgstr "القرض ذو الصلة "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Reset"
|
||||
msgstr "إعادة الضبط "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "Reversal reclassification LT - ST"
|
||||
msgstr "إعادة التصنيف العكسي LT - ST "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields.selection,name:account_loans.selection__account_loan__state__running
|
||||
msgid "Running"
|
||||
msgstr "جاري"
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__message_has_sms_error
|
||||
msgid "SMS Delivery error"
|
||||
msgstr "خطأ في تسليم الرسائل النصية القصيرة "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_search_view
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_search_view
|
||||
msgid "Search Loan"
|
||||
msgstr "البحث عن قرض "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_form_view
|
||||
msgid "Set to Draft"
|
||||
msgstr "تعيين كمسودة"
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.actions.act_window,help:account_loans.action_view_account_loans
|
||||
msgid ""
|
||||
"Set up your amortization schedule, or import it, and let Odoo handle the "
|
||||
"monthly interest and principal adjustments automatically."
|
||||
msgstr ""
|
||||
"قم بإعداد جدول الاستهلاك الخاص بك، أو قم باستيراده ودع أودو يتعامل مع "
|
||||
"تعديلات الفوائد الشهرية وأصل الدين تلقائياً. "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__short_term_account_id
|
||||
msgid "Short Term Account"
|
||||
msgstr "حساب قصير الأجل "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__short_term_theoretical_balance
|
||||
msgid "Short-Term"
|
||||
msgstr "قصير الأجل "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__skip_until_date
|
||||
msgid "Skip until"
|
||||
msgstr "تخطي إلى "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__start_date
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_compute_wizard__start_date
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_list_view
|
||||
msgid "Start Date"
|
||||
msgstr "تاريخ البدء "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__state
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan_line__loan_state
|
||||
msgid "Status"
|
||||
msgstr "الحالة"
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/wizard/account_loan_compute_wizard.py:0
|
||||
msgid "The First Payment Date must be before the end of the loan."
|
||||
msgstr "يجب أن يكون تاريخ القسط الأول قبل نهاية القرض. "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "The amount borrowed must be positive"
|
||||
msgstr "يجب أن يكون المبلغ المقترض موجباً "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "The duration must be positive"
|
||||
msgstr "يجب أن تكون المدة موجبة "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "The interest must be positive"
|
||||
msgstr "يجب أن تكون الفوائد قيمة موجبة "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "The loan accounts should be set."
|
||||
msgstr "يجب أن يتم تعيين حسابات القروض. "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid ""
|
||||
"The loan amount %(loan_amount)s should be equal to the sum of the "
|
||||
"principals: %(principal_sum)s (difference %(principal_difference)s)"
|
||||
msgstr ""
|
||||
"يجب أن يكون مبلغ القرض %(loan_amount)s مساوياً لمجموع أصول الدين: "
|
||||
"%(principal_sum)s(الفرق %(principal_difference)s) "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "The loan date should be earlier than the loan lines date."
|
||||
msgstr "يجب أن يكون تاريخ القرض قبل تاريخ بنود القرض. "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "The loan duration should be equal to the number of loan lines."
|
||||
msgstr "يجب أن تكون مدة القرض مساوية لعدد بنود القرض. "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid ""
|
||||
"The loan interest should be equal to the sum of the loan lines interest."
|
||||
msgstr "يجب أن تكون فائدة القرض مساوية لمجموع فوائد بنود القرض. "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "The loan journal should be set."
|
||||
msgstr "يجب أن يتم تعيين دفتر يومية القرض. "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "The loan name should be set."
|
||||
msgstr "يجب أن يتم تعيين اسم القرض. "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "This entry has been %s"
|
||||
msgstr "هذا القيد %s "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "This entry has been reversed from %s"
|
||||
msgstr "لقد تم عكس هذا القيد من %s "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_list_view
|
||||
msgid "Total Amounts Borrowed"
|
||||
msgstr "إجمالي المبلغ المقترض "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_list_view
|
||||
msgid "Total Outstanding Balance"
|
||||
msgstr "إجمالي المبلغ المستحق "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_list_view
|
||||
msgid "Total interests"
|
||||
msgstr "إجمالي الفوائد "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_list_view
|
||||
msgid "Total payments"
|
||||
msgstr "إجمالي المدفوعات "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.account_loan_line_list_view
|
||||
msgid "Total principals"
|
||||
msgstr "إجمالي أصول الديون "
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-javascript
|
||||
#: code:addons/account_loans/static/src/components/loans/file_upload.xml:0
|
||||
msgid "Upload"
|
||||
msgstr "رفع"
|
||||
|
||||
#. module: account_loans
|
||||
#. odoo-python
|
||||
#: code:addons/account_loans/models/account_loan.py:0
|
||||
msgid "Uploaded file"
|
||||
msgstr "الملف الذي تم رفعه "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,help:account_loans.field_account_loan__skip_until_date
|
||||
msgid ""
|
||||
"Upon confirmation of the loan, Odoo will ignore the loan lines that are up "
|
||||
"to this date (included) and not create entries for them. This is useful if "
|
||||
"you have already manually created entries prior to the creation of this "
|
||||
"loan."
|
||||
msgstr ""
|
||||
"عند تأكيد القرض، سوف يتجاهل أودو بنود القرض حتى هذا التاريخ ( شاملة لهذا "
|
||||
"التاريخ) ولن يقوم بإنشاء قيود لها. يكون ذلك مفيداً إذا كنت قد قمت بالفعل "
|
||||
"بإنشاء قيود يدوياً قبل إنشاء هذا القرض. "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,field_description:account_loans.field_account_loan__website_message_ids
|
||||
msgid "Website Messages"
|
||||
msgstr "رسائل الموقع الإلكتروني "
|
||||
|
||||
#. module: account_loans
|
||||
#: model:ir.model.fields,help:account_loans.field_account_loan__website_message_ids
|
||||
msgid "Website communication history"
|
||||
msgstr "سجل تواصل الموقع الإلكتروني "
|
||||
|
||||
#. module: account_loans
|
||||
#: model_terms:ir.ui.view,arch_db:account_loans.view_account_loan_close_wizard
|
||||
msgid "will be deleted and the loan will be marked as closed."
|
||||
msgstr "سيتم حذفه وسيتم وضع علامة على القرض على أنه مغلق. "
|
||||
|
|
@ -0,0 +1,583 @@
|
|||
|
||||
# Disable Ruff as we simply import the code from pyloan without any modifications
|
||||
# ruff: noqa
|
||||
|
||||
import datetime as dt
|
||||
import calendar as cal
|
||||
import collections
|
||||
from decimal import Decimal
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
Payment = collections.namedtuple('Payment',
|
||||
['date', 'payment_amount', 'interest_amount', 'principal_amount',
|
||||
'special_principal_amount', 'total_principal_amount',
|
||||
'loan_balance_amount'])
|
||||
|
||||
Special_Payment = collections.namedtuple('Special_Payment', ['payment_amount', 'first_payment_date',
|
||||
'special_payment_term',
|
||||
'annual_payments'])
|
||||
Loan_Summary = collections.namedtuple('Loan_Summary', ['loan_amount', 'total_payment_amount',
|
||||
'total_principal_amount',
|
||||
'total_interest_amount',
|
||||
'residual_loan_balance',
|
||||
'repayment_to_principal'])
|
||||
|
||||
|
||||
class Loan(object):
|
||||
|
||||
def __init__(self, loan_amount, interest_rate, loan_term, start_date, payment_amount=None,
|
||||
first_payment_date=None, payment_end_of_month=True, annual_payments=12,
|
||||
interest_only_period=0, compounding_method='30E/360', loan_type='annuity'):
|
||||
|
||||
'''
|
||||
Input validtion for attribute loan_amount
|
||||
'''
|
||||
try:
|
||||
if isinstance(loan_amount, int) or isinstance(loan_amount, float):
|
||||
if loan_amount < 0:
|
||||
raise ValueError('Variable LOAN_AMMOUNT can only be non-negative.')
|
||||
else:
|
||||
raise TypeError(
|
||||
'Variable LOAN_AMOUNT can only be of type integer or float, both non-negative.')
|
||||
|
||||
# handle exceptions for loan_amount
|
||||
except ValueError as val_e:
|
||||
print(val_e)
|
||||
except TypeError as typ_e:
|
||||
print(typ_e)
|
||||
|
||||
else:
|
||||
self.loan_amount = Decimal(str(loan_amount))
|
||||
|
||||
'''
|
||||
Input validation for attribute interet_rate
|
||||
'''
|
||||
try:
|
||||
if isinstance(interest_rate, int) or isinstance(interest_rate, float):
|
||||
if interest_rate < 0:
|
||||
raise ValueError('Variable INTEREST_RATE can only be non-negative.')
|
||||
else:
|
||||
raise TypeError(
|
||||
'Variable INTEREST_RATE can only be of type integer or float, both non-negative.')
|
||||
|
||||
except ValueError as val_e:
|
||||
print(val_e)
|
||||
except TypeError as typ_e:
|
||||
print(typ_e)
|
||||
|
||||
else:
|
||||
self.interest_rate = Decimal(str(interest_rate / 100)).quantize(Decimal(str(0.0001)))
|
||||
|
||||
'''
|
||||
Input validation for attribute loan_term
|
||||
'''
|
||||
try:
|
||||
if isinstance(loan_term, int):
|
||||
if loan_term < 1:
|
||||
raise ValueError(
|
||||
'Variable LOAN_TERM can only be integers greater or equal to 1.')
|
||||
else:
|
||||
raise TypeError('Variable LOAN_TERM can only be of type integer.')
|
||||
|
||||
except ValueError as val_e:
|
||||
print(val_e)
|
||||
except TypeError as typ_e:
|
||||
print(typ_e)
|
||||
|
||||
else:
|
||||
self.loan_term = loan_term
|
||||
|
||||
'''
|
||||
Input validation for attribute payment_amount
|
||||
'''
|
||||
try:
|
||||
if payment_amount is None:
|
||||
pass
|
||||
elif payment_amount is not None and (
|
||||
isinstance(payment_amount, int) or isinstance(payment_amount, float)):
|
||||
if payment_amount < 0:
|
||||
raise ValueError('Variable PAYMENT_AMOUNT can only be non-negative.')
|
||||
else:
|
||||
raise TypeError(
|
||||
'Variable PAYMENT_AMOUNT can only be of type integer or float, both non-negative.')
|
||||
|
||||
except ValueError as val_e:
|
||||
print(val_e)
|
||||
except TypeError as typ_e:
|
||||
print(typ_e)
|
||||
|
||||
else:
|
||||
self.payment_amount = payment_amount
|
||||
|
||||
'''
|
||||
Input validation for attribute start_date
|
||||
'''
|
||||
try:
|
||||
if start_date is None:
|
||||
raise TypeError('Varable START_DATE must by of type date with format YYYY-MM-DD')
|
||||
elif bool(dt.datetime.strptime(start_date, '%Y-%m-%d')) is False:
|
||||
raise ValueError
|
||||
except ValueError as val_e:
|
||||
print(val_e)
|
||||
except TypeError as typ_e:
|
||||
print(typ_e)
|
||||
else:
|
||||
self.start_date = dt.datetime.strptime(start_date, '%Y-%m-%d')
|
||||
|
||||
'''
|
||||
Input validation for attribute first_paymnt_date
|
||||
'''
|
||||
try:
|
||||
if first_payment_date is None:
|
||||
pass
|
||||
elif bool(dt.datetime.strptime(first_payment_date, '%Y-%m-%d')) is False:
|
||||
raise ValueError
|
||||
|
||||
except ValueError as val_e:
|
||||
print(val_e)
|
||||
except TypeError as typ_e:
|
||||
print(typ_e)
|
||||
else:
|
||||
self.first_payment_date = dt.datetime.strptime(first_payment_date,
|
||||
'%Y-%m-%d') if first_payment_date is not None else None
|
||||
|
||||
try:
|
||||
if self.first_payment_date is None:
|
||||
pass
|
||||
elif self.start_date > self.first_payment_date:
|
||||
raise ValueError('FIRST_PAYMENT_DATE cannot be before START_DATE')
|
||||
|
||||
except ValueError as val_e:
|
||||
print(val_e)
|
||||
|
||||
'''
|
||||
Input validation for attribute payment_end_of_month
|
||||
'''
|
||||
try:
|
||||
if not isinstance(payment_end_of_month, bool):
|
||||
raise TypeError(
|
||||
'Variable PAYMENT_END_OF_MONTH can only be of type boolean (either True or False)')
|
||||
|
||||
except TypeError as typ_e:
|
||||
print(typ_e)
|
||||
|
||||
else:
|
||||
self.payment_end_of_month = payment_end_of_month
|
||||
|
||||
'''
|
||||
Input validation for attribute annual_payments
|
||||
'''
|
||||
try:
|
||||
if isinstance(annual_payments, int):
|
||||
if annual_payments not in [12, 4, 2, 1]:
|
||||
raise ValueError(
|
||||
'Attribute ANNUAL_PAYMENTS must be either set to 12, 4, 2 or 1.')
|
||||
else:
|
||||
raise TypeError('Attribute ANNUAL_PAYMENTS must be of type integer.')
|
||||
|
||||
except ValueError as val_e:
|
||||
print(val_e)
|
||||
except TypeError as typ_e:
|
||||
print(typ_e)
|
||||
else:
|
||||
self.annual_payments = annual_payments
|
||||
|
||||
'''
|
||||
Setting of no_of_payments and delta_dt if loan_term and annual_payments are set.
|
||||
'''
|
||||
try:
|
||||
if hasattr(self, 'loan_term') is False or hasattr(self, 'annual_payments') is False:
|
||||
print(self.loan_term)
|
||||
print(self.annual_payments)
|
||||
raise ValueError(
|
||||
'Please make sure that LOAN_TERM and/or ANNUAL_PAYMENTS were correctly defined.11')
|
||||
|
||||
except ValueError as val_e:
|
||||
print(val_e)
|
||||
|
||||
else:
|
||||
self.no_of_payments = self.loan_term * self.annual_payments
|
||||
self.delta_dt = Decimal(str(12 / self.annual_payments))
|
||||
|
||||
'''
|
||||
Input validation for attribute interest_only_period
|
||||
'''
|
||||
try:
|
||||
if isinstance(interest_only_period, int):
|
||||
if interest_only_period < 0:
|
||||
raise ValueError(
|
||||
'Attribute INTEREST_ONLY_PERIOD must be greater or equal to 0.')
|
||||
elif hasattr(self, 'no_of_payments') is False:
|
||||
raise ValueError(
|
||||
'Please make sure that LOAN_TERM and/or ANNUAL_PAYMENTS were correctly defined.')
|
||||
elif hasattr(self,
|
||||
'no_of_payments') is True and self.no_of_payments - interest_only_period < 0:
|
||||
raise ValueError(
|
||||
'Attribute INTEREST_ONLY_PERIOD is greater than product of LOAN_TERM and ANNUAL_PAYMENTS.')
|
||||
else:
|
||||
raise TypeError('Attribute INTEREST_ONLY_PERIOD must be of type integer.')
|
||||
|
||||
except ValueError as val_e:
|
||||
print(val_e)
|
||||
except TypeError as typ_e:
|
||||
print(typ_e)
|
||||
else:
|
||||
self.interest_only_period = interest_only_period
|
||||
|
||||
'''
|
||||
Input validation for attribute compounding_method
|
||||
'''
|
||||
try:
|
||||
if isinstance(compounding_method, str):
|
||||
if compounding_method not in ['30A/360', '30U/360', '30E/360', '30E/360 ISDA',
|
||||
'A/360', 'A/365F', 'A/A ISDA', 'A/A AFB']:
|
||||
raise ValueError(
|
||||
'Attribute COMPOUNDING_METHOD must be set to one of the following: 30A/360, 30U/360, 30E/360, 30E/360 ISDA, A/360, A/365F, A/A ISDA, A/A AFB.')
|
||||
else:
|
||||
raise TypeError('Attribute COMPOUNDING_METHOD must be of type string')
|
||||
|
||||
except ValueError as val_e:
|
||||
print(val_e)
|
||||
except TypeError as typ_e:
|
||||
print(typ_e)
|
||||
else:
|
||||
self.compounding_method = compounding_method
|
||||
|
||||
'''
|
||||
Input validation for attribute loan_type
|
||||
'''
|
||||
try:
|
||||
if isinstance(loan_type, str):
|
||||
if loan_type not in ['annuity', 'linear', 'interest-only']:
|
||||
raise ValueError(
|
||||
'Attribute LOAN_TYPE must be either set to annuity or linear or interest-only.')
|
||||
else:
|
||||
raise TypeError('Attribute LOAN_TYPE must be of type string')
|
||||
|
||||
except ValueError as val_e:
|
||||
print(val_e)
|
||||
except TypeError as typ_e:
|
||||
print(typ_e)
|
||||
else:
|
||||
self.loan_type = loan_type
|
||||
|
||||
# define non-input variables
|
||||
self.special_payments = []
|
||||
self.special_payments_schedule = []
|
||||
|
||||
@staticmethod
|
||||
def _quantize(amount):
|
||||
return Decimal(str(amount)).quantize(Decimal(str(0.01)))
|
||||
|
||||
@staticmethod
|
||||
def _get_day_count(dt1, dt2, method, eom=False):
|
||||
|
||||
def get_julian_day_number(y, m, d):
|
||||
julian_day_count = (1461 * (y + 4800 + (m - 14) / 12)) / 4 + (
|
||||
367 * (m - 2 - 12 * ((m - 14) / 12))) / 12 - (
|
||||
3 * ((y + 4900 + (m - 14) / 12) / 100)) / 4 + d - 32075
|
||||
return julian_day_count
|
||||
|
||||
y1, m1, d1 = dt1.year, dt1.month, dt1.day
|
||||
y2, m2, d2 = dt2.year, dt2.month, dt2.day
|
||||
dt1_eom_day = cal.monthrange(y1, m1)[1]
|
||||
dt2_eom_day = cal.monthrange(y2, m2)[1]
|
||||
|
||||
if method in {'30A/360', '30U/360', '30E/360', '30E/360 ISDA'}:
|
||||
if method == '30A/360':
|
||||
d1 = min(d1, 30)
|
||||
d2 = min(d2, 30) if d1 == 30 else d2
|
||||
if method == '30U/360':
|
||||
if eom and m1 == 2 and d1 == dt1_eom_day and m2 == 2 and d2 == dt2_eom_day:
|
||||
d2 = 30
|
||||
if eom and m1 == 2 and d1 == dt1_eom_day:
|
||||
d1 = 30
|
||||
if d2 == 31 and d1 >= 30:
|
||||
d2 = 30
|
||||
if d1 == 31:
|
||||
d1 = 30
|
||||
if method == '30E/360':
|
||||
if d1 == 31:
|
||||
d1 = 30
|
||||
if d2 == 31:
|
||||
d2 = 30
|
||||
if method == '30E/360 ISDA':
|
||||
if d1 == dt1_eom_day:
|
||||
d1 = 30
|
||||
if d2 == dt2_eom_day and m2 != 2:
|
||||
d2 = 30
|
||||
|
||||
day_count = (360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1))
|
||||
year_days = 360
|
||||
|
||||
if method == 'A/365F':
|
||||
day_count = (dt2 - dt1).days
|
||||
year_days = 365
|
||||
|
||||
if method == 'A/360':
|
||||
day_count = (dt2 - dt1).days
|
||||
year_days = 360
|
||||
|
||||
if method in {'A/A ISDA', 'A/A AFB'}:
|
||||
djn_dt1 = get_julian_day_number(y1, m1, d1)
|
||||
djn_dt2 = get_julian_day_number(y2, m2, d2)
|
||||
if y1 == y2:
|
||||
day_count = djn_dt2 - djn_dt1
|
||||
if method == 'A/A ISDA':
|
||||
year_days = 366 if cal.isleap(y2) else 365
|
||||
if method == 'A/A AFB':
|
||||
year_days = 366 if cal.isleap(y1) and (m1 < 3) else 365
|
||||
if y1 < y2:
|
||||
djn_dt1_eoy = get_julian_day_number(y1, 12, 31)
|
||||
day_count_dt1 = djn_dt1_eoy - djn_dt1
|
||||
if method == 'A/A ISDA':
|
||||
year_days_dt1 = 366 if cal.isleap(y1) else 365
|
||||
if method == 'A/A AFB':
|
||||
year_days_dt1 = 366 if cal.isleap(y1) and (m1 < 3) else 365
|
||||
|
||||
djn_dt2_boy = get_julian_day_number(y2, 1, 1)
|
||||
day_count_dt2 = djn_dt2 - djn_dt2_boy
|
||||
if method == 'A/A ISDA':
|
||||
year_days_dt2 = 366 if cal.isleap(y2) else 365
|
||||
if method == 'A/A AFB':
|
||||
year_days_dt2 = 366 if cal.isleap(y2) and (m2 >= 3) else 365
|
||||
|
||||
diff = y2 - y1 - 1
|
||||
|
||||
day_count = (day_count_dt1 * year_days_dt2) + (day_count_dt2 * year_days_dt1) + (
|
||||
diff * year_days_dt1 * year_days_dt2)
|
||||
year_days = year_days_dt1 * year_days_dt2
|
||||
|
||||
factor = day_count / year_days
|
||||
return factor
|
||||
|
||||
@staticmethod
|
||||
def _get_special_payment_schedule(self, special_payment):
|
||||
no_of_payments = special_payment.special_payment_term * special_payment.annual_payments
|
||||
annual_payments = special_payment.annual_payments
|
||||
dt0 = dt.datetime.strptime(special_payment.first_payment_date, '%Y-%m-%d')
|
||||
|
||||
special_payment_amount = self._quantize(special_payment.payment_amount)
|
||||
initial_special_payment = Payment(date=dt0, payment_amount=self._quantize(0),
|
||||
interest_amount=self._quantize(0),
|
||||
principal_amount=self._quantize(0),
|
||||
special_principal_amount=special_payment_amount,
|
||||
total_principal_amount=self._quantize(0),
|
||||
loan_balance_amount=self._quantize(0))
|
||||
special_payment_schedule = [initial_special_payment]
|
||||
|
||||
for i in range(1, no_of_payments):
|
||||
date = dt0 + relativedelta(months=i * 12 / annual_payments)
|
||||
special_payment = Payment(date=date, payment_amount=self._quantize(0),
|
||||
interest_amount=self._quantize(0),
|
||||
principal_amount=self._quantize(0),
|
||||
special_principal_amount=special_payment_amount,
|
||||
total_principal_amount=self._quantize(0),
|
||||
loan_balance_amount=self._quantize(0))
|
||||
special_payment_schedule.append(special_payment)
|
||||
|
||||
return special_payment_schedule
|
||||
|
||||
'''
|
||||
Define method that calculates payment schedule
|
||||
'''
|
||||
|
||||
def get_payment_schedule(self):
|
||||
|
||||
attributes = ['loan_amount', 'interest_rate', 'loan_term', 'payment_amount', 'start_date',
|
||||
'first_payment_date', 'payment_end_of_month', 'annual_payments',
|
||||
'no_of_payments', 'delta_dt', 'interest_only_period', 'compounding_method',
|
||||
'special_payments', 'special_payments_schedule']
|
||||
raise_error_flag = 0
|
||||
for attribute in attributes:
|
||||
if hasattr(self, attribute) is False:
|
||||
raise_error_flag = raise_error_flag + 1
|
||||
|
||||
try:
|
||||
if raise_error_flag != 0:
|
||||
raise ValueError(
|
||||
'Necessary attributes are not well defined, please review your inputs')
|
||||
|
||||
except ValueError as val_e:
|
||||
print(val_e)
|
||||
else:
|
||||
initial_payment = Payment(date=self.start_date, payment_amount=self._quantize(0),
|
||||
interest_amount=self._quantize(0),
|
||||
principal_amount=self._quantize(0),
|
||||
special_principal_amount=self._quantize(0),
|
||||
total_principal_amount=self._quantize(0),
|
||||
loan_balance_amount=self._quantize(self.loan_amount))
|
||||
payment_schedule = [initial_payment]
|
||||
interest_only_period = self.interest_only_period
|
||||
|
||||
# take care of loan type
|
||||
if self.loan_type == 'annuity':
|
||||
if self.payment_amount is None:
|
||||
regular_principal_payment_amount = self.loan_amount * (
|
||||
(self.interest_rate / self.annual_payments) * (
|
||||
1 + (self.interest_rate / self.annual_payments)) ** (
|
||||
(self.no_of_payments - interest_only_period))) / ((1 + (
|
||||
self.interest_rate / self.annual_payments)) ** ((
|
||||
self.no_of_payments - interest_only_period)) - 1)
|
||||
else:
|
||||
regular_principal_payment_amount = self.payment_amount
|
||||
|
||||
if self.loan_type == 'linear':
|
||||
if self.payment_amount is None:
|
||||
regular_principal_payment_amount = self.loan_amount / (
|
||||
self.no_of_payments - self.interest_only_period)
|
||||
else:
|
||||
regular_principal_payment_amount = self.payment_amount
|
||||
|
||||
if self.loan_type == 'interest-only':
|
||||
regular_principal_payment_amount = 0
|
||||
interest_only_period = self.no_of_payments
|
||||
|
||||
if self.first_payment_date is None:
|
||||
if self.payment_end_of_month == True:
|
||||
if self.start_date.day == \
|
||||
cal.monthrange(self.start_date.year, self.start_date.month)[1]:
|
||||
dt0 = self.start_date
|
||||
else:
|
||||
dt0 = dt.datetime(self.start_date.year, self.start_date.month,
|
||||
cal.monthrange(self.start_date.year,
|
||||
self.start_date.month)[1], 0,
|
||||
0) + relativedelta(months=-12 / self.annual_payments)
|
||||
else:
|
||||
dt0 = self.start_date
|
||||
else:
|
||||
dt0 = max(self.first_payment_date, self.start_date) + relativedelta(
|
||||
months=-12 / self.annual_payments)
|
||||
|
||||
# take care of special payments
|
||||
special_payments_schedule_raw = []
|
||||
special_payments_schedule = []
|
||||
special_payments_dates = []
|
||||
if len(self.special_payments_schedule) > 0:
|
||||
for i in range(len(self.special_payments_schedule)):
|
||||
for j in range(len(self.special_payments_schedule[i])):
|
||||
special_payments_schedule_raw.append(
|
||||
[self.special_payments_schedule[i][j].date,
|
||||
self.special_payments_schedule[i][j].special_principal_amount])
|
||||
if self.special_payments_schedule[i][j].date not in special_payments_dates:
|
||||
special_payments_dates.append(self.special_payments_schedule[i][j].date)
|
||||
|
||||
for i in range(len(special_payments_dates)):
|
||||
amt = self._quantize(str(0))
|
||||
for j in range(len(special_payments_schedule_raw)):
|
||||
if special_payments_schedule_raw[j][0] == special_payments_dates[i]:
|
||||
amt += special_payments_schedule_raw[j][1]
|
||||
special_payments_schedule.append([special_payments_dates[i], amt])
|
||||
|
||||
# calculate payment schedule
|
||||
m = 0
|
||||
for i in range(1, self.no_of_payments + 1):
|
||||
|
||||
date = dt0 + relativedelta(months=i * 12 / self.annual_payments)
|
||||
if self.payment_end_of_month == True and self.first_payment_date is None:
|
||||
eom_day = cal.monthrange(date.year, date.month)[1]
|
||||
date = date.replace(day=eom_day) # dt.datetime(date.year,date.month,eom_day)
|
||||
|
||||
special_principal_amount = self._quantize(0)
|
||||
bop_date = payment_schedule[(i + m) - 1].date
|
||||
compounding_factor = Decimal(
|
||||
str(self._get_day_count(bop_date, date, self.compounding_method,
|
||||
eom=self.payment_end_of_month)))
|
||||
balance_bop = self._quantize(payment_schedule[(i + m) - 1].loan_balance_amount)
|
||||
|
||||
for j in range(len(special_payments_schedule)):
|
||||
if date == special_payments_schedule[j][0]:
|
||||
special_principal_amount = special_payments_schedule[j][1]
|
||||
if (bop_date < special_payments_schedule[j][0] and special_payments_schedule[j][
|
||||
0] < date):
|
||||
# handle special payment inserts
|
||||
compounding_factor = Decimal(
|
||||
str(self._get_day_count(bop_date, special_payments_schedule[j][0],
|
||||
self.compounding_method,
|
||||
eom=self.payment_end_of_month)))
|
||||
interest_amount = self._quantize(0) if balance_bop == Decimal(
|
||||
str(0)) else self._quantize(
|
||||
balance_bop * self.interest_rate * compounding_factor)
|
||||
principal_amount = self._quantize(0)
|
||||
special_principal_amount = self._quantize(0) if balance_bop == Decimal(
|
||||
str(0)) else min(special_payments_schedule[j][1] - interest_amount,
|
||||
balance_bop)
|
||||
total_principal_amount = min(principal_amount + special_principal_amount,
|
||||
balance_bop)
|
||||
total_payment_amount = total_principal_amount + interest_amount
|
||||
balance_eop = max(balance_bop - total_principal_amount, self._quantize(0))
|
||||
payment = Payment(date=special_payments_schedule[j][0],
|
||||
payment_amount=total_payment_amount,
|
||||
interest_amount=interest_amount,
|
||||
principal_amount=principal_amount,
|
||||
special_principal_amount=special_principal_amount,
|
||||
total_principal_amount=special_principal_amount,
|
||||
loan_balance_amount=balance_eop)
|
||||
payment_schedule.append(payment)
|
||||
m += 1
|
||||
# handle regular payment inserts : update bop_date and bop_date, and special_principal_amount
|
||||
bop_date = special_payments_schedule[j][0]
|
||||
balance_bop = balance_eop
|
||||
special_principal_amount = self._quantize(0)
|
||||
compounding_factor = Decimal(
|
||||
str(self._get_day_count(bop_date, date, self.compounding_method,
|
||||
eom=self.payment_end_of_month)))
|
||||
|
||||
interest_amount = self._quantize(0) if balance_bop == Decimal(
|
||||
str(0)) else self._quantize(
|
||||
balance_bop * self.interest_rate * compounding_factor)
|
||||
|
||||
principal_amount = self._quantize(0) if balance_bop == Decimal(
|
||||
str(0)) or interest_only_period >= i else min(
|
||||
self._quantize(regular_principal_payment_amount) - (
|
||||
interest_amount if self.loan_type == 'annuity' else 0), balance_bop)
|
||||
special_principal_amount = min(balance_bop - principal_amount,
|
||||
special_principal_amount) if interest_only_period < i else self._quantize(
|
||||
0)
|
||||
total_principal_amount = min(principal_amount + special_principal_amount,
|
||||
balance_bop)
|
||||
total_payment_amount = total_principal_amount + interest_amount
|
||||
balance_eop = max(balance_bop - total_principal_amount, self._quantize(0))
|
||||
|
||||
payment = Payment(date=date, payment_amount=total_payment_amount,
|
||||
interest_amount=interest_amount,
|
||||
principal_amount=principal_amount,
|
||||
special_principal_amount=special_principal_amount,
|
||||
total_principal_amount=total_principal_amount,
|
||||
loan_balance_amount=balance_eop)
|
||||
payment_schedule.append(payment)
|
||||
|
||||
return payment_schedule
|
||||
|
||||
def add_special_payment(self, payment_amount, first_payment_date, special_payment_term,
|
||||
annual_payments):
|
||||
special_payment = Special_Payment(payment_amount=payment_amount,
|
||||
first_payment_date=first_payment_date,
|
||||
special_payment_term=special_payment_term,
|
||||
annual_payments=annual_payments)
|
||||
self.special_payments.append(special_payment)
|
||||
self.special_payments_schedule.append(
|
||||
self._get_special_payment_schedule(self, special_payment))
|
||||
|
||||
def get_loan_summary(self):
|
||||
payment_schedule = self.get_payment_schedule()
|
||||
total_payment_amount = 0
|
||||
total_interest_amount = 0
|
||||
total_principal_amount = 0
|
||||
repayment_to_principal = 0
|
||||
|
||||
for payment in payment_schedule:
|
||||
total_payment_amount += payment.payment_amount
|
||||
total_interest_amount += payment.interest_amount
|
||||
total_principal_amount += payment.total_principal_amount
|
||||
|
||||
repayment_to_principal = self._quantize(total_payment_amount / total_principal_amount)
|
||||
loan_summary = Loan_Summary(loan_amount=self._quantize(self.loan_amount),
|
||||
total_payment_amount=total_payment_amount,
|
||||
total_principal_amount=total_principal_amount,
|
||||
total_interest_amount=total_interest_amount,
|
||||
residual_loan_balance=self._quantize(
|
||||
self.loan_amount - total_principal_amount),
|
||||
repayment_to_principal=repayment_to_principal)
|
||||
|
||||
return loan_summary
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
from . import account_asset
|
||||
from . import account_asset_group
|
||||
from . import account_journal
|
||||
from . import account_loan
|
||||
from . import account_loan_line
|
||||
from . import account_move
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
class AccountAsset(models.Model):
|
||||
_inherit = 'account.asset'
|
||||
|
||||
linked_loans_ids = fields.One2many(related='asset_group_id.linked_loan_ids')
|
||||
count_linked_loans = fields.Integer(compute="_compute_count_linked_loans")
|
||||
|
||||
@api.depends('linked_loans_ids')
|
||||
def _compute_count_linked_loans(self):
|
||||
for asset in self:
|
||||
asset.count_linked_loans = len(asset.linked_loans_ids)
|
||||
|
||||
def action_open_linked_loans(self):
|
||||
return {
|
||||
'name': _('Linked loans'),
|
||||
'view_mode': 'list,form',
|
||||
'res_model': 'account.loan',
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'current',
|
||||
'domain': [('id', 'in', self.linked_loans_ids.ids)],
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class AccountAssetGroup(models.Model):
|
||||
_inherit = 'account.asset.group'
|
||||
|
||||
linked_loan_ids = fields.One2many('account.loan', 'asset_group_id', string='Related Loans')
|
||||
count_linked_loans = fields.Integer(compute="_compute_count_linked_loans")
|
||||
|
||||
@api.depends('linked_loan_ids')
|
||||
def _compute_count_linked_loans(self):
|
||||
count_per_asset_group = {
|
||||
asset_group.id: count
|
||||
for asset_group, count in self.env['account.loan']._read_group(
|
||||
domain=[
|
||||
('asset_group_id', 'in', self.ids),
|
||||
],
|
||||
groupby=['asset_group_id'],
|
||||
aggregates=['__count'],
|
||||
)
|
||||
}
|
||||
for asset_group in self:
|
||||
asset_group.count_linked_loans = count_per_asset_group.get(asset_group.id, 0)
|
||||
|
||||
def action_open_linked_loans(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'name': self.name,
|
||||
'view_mode': 'list,form',
|
||||
'res_model': 'account.loan',
|
||||
'type': 'ir.actions.act_window',
|
||||
'domain': [('id', 'in', self.linked_loan_ids.ids)],
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
from odoo import models, fields
|
||||
|
||||
|
||||
class AccountJournal(models.Model):
|
||||
_inherit = "account.journal"
|
||||
|
||||
loan_properties_definition = fields.PropertiesDefinition('Model Properties')
|
||||
|
|
@ -0,0 +1,426 @@
|
|||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import fields, models, api, _, Command
|
||||
from odoo.tools import float_compare
|
||||
from odoo.tools.misc import format_date
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
|
||||
class AccountLoan(models.Model):
|
||||
_name = 'account.loan'
|
||||
_description = 'Loan'
|
||||
_inherit = ['mail.thread']
|
||||
_order = 'date'
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields_list):
|
||||
values = super().default_get(fields_list)
|
||||
if all(field not in fields_list for field in ['expense_account_id', 'long_term_account_id', 'short_term_account_id', 'journal_id']):
|
||||
return values
|
||||
previous_loan = self.search([
|
||||
('company_id', '=', self.env.company.id),
|
||||
('expense_account_id', '!=', False),
|
||||
('long_term_account_id', '!=', False),
|
||||
('short_term_account_id', '!=', False),
|
||||
('journal_id', '!=', False),
|
||||
], limit=1)
|
||||
if previous_loan:
|
||||
values['expense_account_id'] = previous_loan.expense_account_id.id
|
||||
values['long_term_account_id'] = previous_loan.long_term_account_id.id
|
||||
values['short_term_account_id'] = previous_loan.short_term_account_id.id
|
||||
values['journal_id'] = previous_loan.journal_id.id
|
||||
return values
|
||||
|
||||
name = fields.Char("Name", required=True, index="trigram", tracking=True)
|
||||
company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one(related='company_id.currency_id')
|
||||
active = fields.Boolean(default=True)
|
||||
state = fields.Selection(
|
||||
string="Status",
|
||||
selection=[
|
||||
('draft', 'Draft'),
|
||||
('running', 'Running'),
|
||||
('closed', 'Closed'),
|
||||
('cancelled', 'Cancelled'),
|
||||
],
|
||||
default='draft',
|
||||
required=True,
|
||||
tracking=True,
|
||||
)
|
||||
date = fields.Date('Loan Date', index="btree_not_null")
|
||||
amount_borrowed = fields.Monetary(string='Amount Borrowed', tracking=True)
|
||||
interest = fields.Monetary(string='Interest')
|
||||
duration = fields.Integer('Duration')
|
||||
skip_until_date = fields.Date(
|
||||
string='Skip until',
|
||||
help='Upon confirmation of the loan, Odoo will ignore the loan lines that are up to this date (included) and not create entries for them. '
|
||||
'This is useful if you have already manually created entries prior to the creation of this loan.'
|
||||
)
|
||||
|
||||
long_term_account_id = fields.Many2one('account.account', string='Long Term Account', tracking=True)
|
||||
short_term_account_id = fields.Many2one('account.account', string='Short Term Account', tracking=True)
|
||||
expense_account_id = fields.Many2one(
|
||||
comodel_name='account.account',
|
||||
string='Expense Account',
|
||||
tracking=True,
|
||||
domain="[('account_type', 'in', ('expense', 'expense_depreciation'))]",
|
||||
)
|
||||
journal_id = fields.Many2one(
|
||||
comodel_name='account.journal',
|
||||
string='Journal',
|
||||
domain="[('type', '=', 'general')]",
|
||||
)
|
||||
asset_group_id = fields.Many2one('account.asset.group', string='Asset Group', tracking=True, index=True)
|
||||
loan_properties = fields.Properties('Properties', definition='journal_id.loan_properties_definition')
|
||||
|
||||
line_ids = fields.One2many('account.loan.line', 'loan_id', string='Loan Lines') # Amortization schedule
|
||||
|
||||
# Computed fields
|
||||
display_name = fields.Char("Loan name", compute='_compute_display_name', store=True) # stored for pivot view
|
||||
start_date = fields.Date(compute='_compute_start_end_date')
|
||||
end_date = fields.Date(compute='_compute_start_end_date')
|
||||
is_wrong_date = fields.Boolean(compute='_compute_is_wrong_date')
|
||||
amount_borrowed_difference = fields.Monetary(compute='_compute_amount_borrowed_difference')
|
||||
interest_difference = fields.Monetary(compute='_compute_interest_difference')
|
||||
duration_difference = fields.Integer(compute='_compute_duration_difference')
|
||||
outstanding_balance = fields.Monetary(string='Outstanding Balance', compute='_compute_outstanding_balance') # Based on the posted entries
|
||||
nb_posted_entries = fields.Integer(compute='_compute_nb_posted_entries')
|
||||
linked_assets_ids = fields.One2many(
|
||||
comodel_name='account.asset',
|
||||
string="Linked Assets",
|
||||
compute='_compute_linked_assets',
|
||||
)
|
||||
count_linked_assets = fields.Integer(compute="_compute_linked_assets")
|
||||
|
||||
# Constrains
|
||||
@api.constrains('amount_borrowed', 'interest', 'duration')
|
||||
def _require_positive_values(self):
|
||||
for loan in self:
|
||||
if float_compare(loan.amount_borrowed, 0.0, precision_rounding=loan.currency_id.rounding) < 0:
|
||||
raise ValidationError(_('The amount borrowed must be positive'))
|
||||
if float_compare(loan.interest, 0.0, precision_rounding=loan.currency_id.rounding) < 0:
|
||||
raise ValidationError(_('The interest must be positive'))
|
||||
if loan.duration < 0:
|
||||
raise ValidationError(_('The duration must be positive'))
|
||||
|
||||
# Compute methods
|
||||
@api.depends('name', 'start_date', 'end_date')
|
||||
def _compute_display_name(self):
|
||||
for loan in self:
|
||||
if loan.name and loan.start_date and loan.end_date:
|
||||
start_date = format_date(self.env, loan.start_date, date_format='MM y')
|
||||
end_date = format_date(self.env, loan.end_date, date_format='MM y')
|
||||
loan.display_name = f"{loan.name}: {start_date} - {end_date}"
|
||||
else:
|
||||
loan.display_name = loan.name
|
||||
|
||||
@api.depends('line_ids')
|
||||
def _compute_start_end_date(self):
|
||||
for loan in self:
|
||||
if loan.line_ids:
|
||||
loan.start_date = loan.line_ids[0].date
|
||||
loan.end_date = loan.line_ids[-1].date
|
||||
else:
|
||||
loan.start_date = False
|
||||
loan.end_date = False
|
||||
|
||||
@api.depends('date')
|
||||
def _compute_is_wrong_date(self):
|
||||
for loan in self:
|
||||
loan.is_wrong_date = not loan.date or any(date < loan.date for date in loan.line_ids.mapped('date'))
|
||||
|
||||
@api.depends('amount_borrowed', 'line_ids.principal', 'currency_id')
|
||||
def _compute_amount_borrowed_difference(self):
|
||||
for loan in self:
|
||||
if loan.currency_id:
|
||||
loan.amount_borrowed_difference = abs(loan.amount_borrowed - loan.currency_id.round(sum(loan.line_ids.mapped('principal'))))
|
||||
else:
|
||||
loan.amount_borrowed_difference = 0
|
||||
|
||||
@api.depends('interest', 'line_ids.interest')
|
||||
def _compute_interest_difference(self):
|
||||
for loan in self:
|
||||
if loan.interest and loan.line_ids:
|
||||
loan.interest_difference = loan.interest - loan.currency_id.round(sum(loan.line_ids.mapped('interest')))
|
||||
else:
|
||||
loan.interest_difference = 0
|
||||
|
||||
@api.depends('duration', 'line_ids')
|
||||
def _compute_duration_difference(self):
|
||||
for loan in self:
|
||||
loan.duration_difference = loan.duration - len(loan.line_ids)
|
||||
|
||||
@api.depends('line_ids.generated_move_ids')
|
||||
def _compute_nb_posted_entries(self):
|
||||
for loan in self:
|
||||
loan.nb_posted_entries = len(loan.line_ids.generated_move_ids.filtered(lambda m: m.state == 'posted'))
|
||||
|
||||
@api.depends('amount_borrowed', 'line_ids.principal', 'state', 'line_ids.is_payment_move_posted')
|
||||
def _compute_outstanding_balance(self):
|
||||
for loan in self:
|
||||
outstanding_balance = loan.amount_borrowed
|
||||
if loan.state == 'running':
|
||||
for line in loan.line_ids:
|
||||
if line.is_payment_move_posted or (loan.skip_until_date and line.date < loan.skip_until_date):
|
||||
outstanding_balance -= line.principal
|
||||
loan.outstanding_balance = outstanding_balance
|
||||
|
||||
@api.depends('asset_group_id')
|
||||
def _compute_linked_assets(self):
|
||||
for loan in self:
|
||||
loan.linked_assets_ids = loan.asset_group_id.linked_asset_ids
|
||||
loan.count_linked_assets = len(loan.linked_assets_ids)
|
||||
|
||||
# Action methods
|
||||
def action_confirm(self):
|
||||
for loan in self:
|
||||
# Verifications
|
||||
if not loan.name:
|
||||
raise UserError(_("The loan name should be set."))
|
||||
if loan.is_wrong_date:
|
||||
raise UserError(_("The loan date should be earlier than the loan lines date."))
|
||||
if float_compare(loan.amount_borrowed_difference, 0.0, precision_rounding=loan.currency_id.rounding) != 0:
|
||||
raise UserError(_(
|
||||
"The loan amount %(loan_amount)s should be equal to the sum of the principals: %(principal_sum)s (difference %(principal_difference)s)",
|
||||
loan_amount=loan.currency_id.format(loan.amount_borrowed),
|
||||
principal_sum=loan.currency_id.format(sum(loan.line_ids.mapped('principal'))),
|
||||
principal_difference=loan.currency_id.format(loan.amount_borrowed_difference),
|
||||
))
|
||||
if float_compare(loan.interest_difference, 0.0, precision_rounding=loan.currency_id.rounding) != 0:
|
||||
raise UserError(_("The loan interest should be equal to the sum of the loan lines interest."))
|
||||
if loan.duration_difference != 0:
|
||||
raise UserError(_("The loan duration should be equal to the number of loan lines."))
|
||||
if not loan.long_term_account_id or not loan.short_term_account_id or not loan.expense_account_id:
|
||||
raise UserError(_("The loan accounts should be set."))
|
||||
if not loan.journal_id:
|
||||
raise UserError(_("The loan journal should be set."))
|
||||
|
||||
payment_moves_values = []
|
||||
reclassification_moves_values = []
|
||||
reclassification_reversed_moves_values = []
|
||||
for i, line in enumerate(loan.line_ids):
|
||||
if loan.skip_until_date and line.date < loan.skip_until_date:
|
||||
continue
|
||||
|
||||
# Principal and interest (to match with the bank statement)
|
||||
payment_moves_values.append({
|
||||
'company_id': loan.company_id.id,
|
||||
'auto_post': 'at_date',
|
||||
'generating_loan_line_id': line.id,
|
||||
'is_loan_payment_move': True,
|
||||
'date': line.date + relativedelta(day=31),
|
||||
'journal_id': loan.journal_id.id,
|
||||
'ref': f"{loan.name} - {_('Principal & Interest')} {format_date(self.env, line.date, date_format='MM/y')}",
|
||||
'line_ids': [
|
||||
Command.create({
|
||||
'account_id': loan.long_term_account_id.id,
|
||||
'debit': line.principal,
|
||||
'name': f"{loan.name} - {_('Principal')} {format_date(self.env, line.date, date_format='MM/y')}",
|
||||
}),
|
||||
Command.create({
|
||||
'account_id': loan.short_term_account_id.id,
|
||||
'credit': line.payment,
|
||||
'name': f"{loan.name} - {_('Due')} {format_date(self.env, line.date, date_format='MM/y')} "
|
||||
f"({_('Principal')} {loan.currency_id.format(line.principal)} + {_('Interest')} {loan.currency_id.format(line.interest)})",
|
||||
}),
|
||||
Command.create({
|
||||
'account_id': loan.expense_account_id.id,
|
||||
'debit': line.interest,
|
||||
'name': f"{loan.name} - {_('Interest')} {format_date(self.env, line.date, date_format='MM/y')}",
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
# Principal reclassification Long Term - Short Term
|
||||
if line == loan.line_ids[-1]:
|
||||
break
|
||||
next_lines = loan.line_ids[i + 1: i + 13] # 13 = 1 (start offset) + 12 months
|
||||
from_date = format_date(self.env, next_lines[0].date, date_format='MM/y')
|
||||
to_date = format_date(self.env, next_lines[-1].date, date_format='MM/y')
|
||||
common_reclassification_values = {
|
||||
'company_id': loan.company_id.id,
|
||||
'auto_post': 'at_date',
|
||||
'generating_loan_line_id': line.id,
|
||||
'is_loan_payment_move': False,
|
||||
'journal_id': loan.journal_id.id,
|
||||
}
|
||||
reclassification_moves_values.append({
|
||||
**common_reclassification_values,
|
||||
'date': line.date + relativedelta(day=31),
|
||||
'ref': f"{loan.name} - {_('Reclassification LT - ST')} {from_date} to {to_date}",
|
||||
'line_ids': [
|
||||
Command.create({
|
||||
'account_id': loan.long_term_account_id.id,
|
||||
'debit': sum(next_lines.mapped('principal')),
|
||||
'name': f"{loan.name} - {_('Reclassification LT - ST')} {from_date} to {to_date} (To {loan.short_term_account_id.code})",
|
||||
}),
|
||||
Command.create({
|
||||
'account_id': loan.short_term_account_id.id,
|
||||
'credit': sum(next_lines.mapped('principal')),
|
||||
'name': f"{loan.name} - {_('Reclassification LT - ST')} {from_date} to {to_date} (From {loan.long_term_account_id.code})",
|
||||
}),
|
||||
],
|
||||
})
|
||||
# Manually create the reverse (instead of using _reverse_moves()) for optimization reasons
|
||||
reclassification_reversed_moves_values.append({
|
||||
**common_reclassification_values,
|
||||
'date': line.date + relativedelta(day=31) + relativedelta(days=1), # first day of next month
|
||||
'ref': f"{loan.name} - {_('Reversal reclassification LT - ST')} {from_date} to {to_date}",
|
||||
'line_ids': [
|
||||
Command.create({
|
||||
'account_id': loan.long_term_account_id.id,
|
||||
'credit': sum(next_lines.mapped('principal')),
|
||||
'name': f"{loan.name} - {_('Reversal reclassification LT - ST')} {from_date} to {to_date} (To {loan.short_term_account_id.code})",
|
||||
}),
|
||||
Command.create({
|
||||
'account_id': loan.short_term_account_id.id,
|
||||
'debit': sum(next_lines.mapped('principal')),
|
||||
'name': f"{loan.name} - {_('Reversal reclassification LT - ST')} {from_date} to {to_date} (From {loan.long_term_account_id.code})",
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
def post_moves(moves):
|
||||
moves.filtered(lambda m: m.date <= fields.Date.context_today(self)).action_post()
|
||||
|
||||
payment_moves = self.env['account.move'].create(payment_moves_values)
|
||||
reclassification_moves = self.env['account.move'].create(reclassification_moves_values)
|
||||
reclassification_reversed_moves = self.env['account.move'].create(reclassification_reversed_moves_values)
|
||||
post_moves(payment_moves | reclassification_moves | reclassification_reversed_moves)
|
||||
|
||||
for (reclassification_move, reclassification_reversed_move) in zip(reclassification_moves, reclassification_reversed_moves):
|
||||
reclassification_reversed_move.reversed_entry_id = reclassification_move
|
||||
reclassification_reversed_move.message_post(body=_('This entry has been reversed from %s', reclassification_move._get_html_link()))
|
||||
|
||||
bodies = {}
|
||||
for move, reverse in zip(reclassification_moves, reclassification_reversed_moves):
|
||||
bodies[move.id] = _('This entry has been %s', reverse._get_html_link(title=_("reversed")))
|
||||
reclassification_moves._message_log_batch(bodies=bodies)
|
||||
|
||||
if any(m.state != 'posted' for m in payment_moves | reclassification_moves | reclassification_reversed_moves):
|
||||
loan.state = 'running'
|
||||
|
||||
def action_upload_amortization_schedule(self, attachment_id):
|
||||
"""Called when uploading an amortization schedule file"""
|
||||
attachment = self.env['ir.attachment'].browse(attachment_id)
|
||||
loan = self or self.create({
|
||||
'name': attachment.name,
|
||||
})
|
||||
loan.line_ids.unlink()
|
||||
loan.message_post(body=_('Uploaded file'), attachment_ids=[attachment.id])
|
||||
import_wizard = self.env['base_import.import'].create({
|
||||
'res_model': 'account.loan.line',
|
||||
'file': attachment.raw,
|
||||
'file_name': attachment.name,
|
||||
'file_type': attachment.mimetype,
|
||||
})
|
||||
ctx = {
|
||||
**self.env.context,
|
||||
'wizard_id': import_wizard.id,
|
||||
'default_loan_id': loan.id,
|
||||
}
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'import_loan',
|
||||
'params': {
|
||||
'model': 'account.loan.line',
|
||||
'context': ctx,
|
||||
'filename': attachment.name,
|
||||
}
|
||||
}
|
||||
|
||||
def action_file_uploaded(self):
|
||||
"""Called after the amortization schedule has been imported by the wizard"""
|
||||
self.ensure_one()
|
||||
action = {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _("Loans"),
|
||||
'res_model': 'account.loan',
|
||||
'views': [(False, 'list'), (False, 'form')],
|
||||
'target': 'self',
|
||||
}
|
||||
if self.line_ids:
|
||||
self.amount_borrowed = sum(self.line_ids.mapped('principal'))
|
||||
self.interest = sum(self.line_ids.mapped('interest'))
|
||||
self.duration = len(self.line_ids)
|
||||
self.date = self.line_ids[0].date
|
||||
return {
|
||||
**action,
|
||||
'res_id': self.id,
|
||||
'views': [(False, 'form')],
|
||||
}
|
||||
return action
|
||||
|
||||
def action_open_compute_wizard(self):
|
||||
if not self:
|
||||
raise UserError(_("Please add a name before computing the loan"))
|
||||
wizard = self.env['account.loan.compute.wizard'].create({
|
||||
'loan_id': self.id,
|
||||
'loan_amount': self.amount_borrowed,
|
||||
})
|
||||
if self.date:
|
||||
wizard["start_date"] = self.date
|
||||
wizard["first_payment_date"] = self.date.replace(day=1) + relativedelta(months=1) # first day of next month
|
||||
return {
|
||||
'name': _("Compute New Loan"),
|
||||
'res_id': wizard.id,
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'account.loan.compute.wizard',
|
||||
'target': 'new',
|
||||
'views': [[False, 'form']],
|
||||
'context': self.env.context,
|
||||
}
|
||||
|
||||
def action_reset(self):
|
||||
self.ensure_one()
|
||||
self.line_ids.unlink()
|
||||
|
||||
def action_close(self):
|
||||
self.ensure_one()
|
||||
wizard = self.env['account.loan.close.wizard'].create({
|
||||
'loan_id': self.id,
|
||||
})
|
||||
return {
|
||||
'name': _('Close'),
|
||||
'view_mode': 'form',
|
||||
'res_model': 'account.loan.close.wizard',
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'new',
|
||||
'res_id': wizard.id,
|
||||
}
|
||||
|
||||
def action_cancel(self):
|
||||
self.line_ids.generated_move_ids.filtered(lambda m: m.state != 'cancel')._unlink_or_reverse()
|
||||
self.state = 'cancelled'
|
||||
|
||||
def action_set_to_draft(self):
|
||||
self.line_ids.generated_move_ids.filtered(lambda m: m.state != 'cancel')._unlink_or_reverse()
|
||||
self.state = 'draft'
|
||||
|
||||
def action_open_loan_entries(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'name': _('Loan Entries'),
|
||||
'view_mode': 'list,form',
|
||||
'res_model': 'account.move',
|
||||
'views': [(self.env.ref('account_loans.account_loan_view_account_move_list_view').id, 'list'), (False, 'form')],
|
||||
'type': 'ir.actions.act_window',
|
||||
'domain': [('id', 'in', self.line_ids.generated_move_ids.ids)],
|
||||
}
|
||||
|
||||
def action_open_linked_assets(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'name': _('Linked Assets'),
|
||||
'view_mode': 'list,form',
|
||||
'res_model': 'account.asset',
|
||||
'type': 'ir.actions.act_window',
|
||||
'domain': [('id', 'in', self.linked_assets_ids.ids)],
|
||||
}
|
||||
|
||||
# Model methods
|
||||
@api.ondelete(at_uninstall=False)
|
||||
def _unlink_loan(self):
|
||||
for loan in self:
|
||||
loan.line_ids.generated_move_ids._unlink_or_reverse()
|
||||
loan.line_ids.unlink()
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class AccountLoanLine(models.Model):
|
||||
_name = 'account.loan.line'
|
||||
_description = 'Loan Line'
|
||||
_order = 'date, id'
|
||||
|
||||
sequence = fields.Integer("#", compute='_compute_sequence')
|
||||
loan_id = fields.Many2one('account.loan', string='Loan', required=True, ondelete="cascade", index=True)
|
||||
loan_name = fields.Char(related='loan_id.name')
|
||||
loan_state = fields.Selection(related='loan_id.state')
|
||||
loan_date = fields.Date(related='loan_id.date')
|
||||
loan_asset_group_id = fields.Many2one(related='loan_id.asset_group_id')
|
||||
active = fields.Boolean(related='loan_id.active')
|
||||
company_id = fields.Many2one(related='loan_id.company_id')
|
||||
currency_id = fields.Many2one(related='company_id.currency_id')
|
||||
date = fields.Date('Date', required=True)
|
||||
principal = fields.Monetary(string='Principal')
|
||||
interest = fields.Monetary(string='Interest')
|
||||
payment = fields.Monetary(
|
||||
string='Payment',
|
||||
compute='_compute_payment',
|
||||
store=True, # stored for pivot view
|
||||
)
|
||||
outstanding_balance = fields.Monetary(
|
||||
string='Outstanding Balance',
|
||||
compute='_compute_outstanding_balance',
|
||||
) # theoretical outstanding balance at the date of the line
|
||||
long_term_theoretical_balance = fields.Monetary(
|
||||
string='Long-Term',
|
||||
compute='_compute_theoretical_balances',
|
||||
store=True, # stored for pivot view
|
||||
)
|
||||
short_term_theoretical_balance = fields.Monetary(
|
||||
string='Short-Term',
|
||||
compute='_compute_theoretical_balances',
|
||||
store=True, # stored for pivot view
|
||||
)
|
||||
generated_move_ids = fields.One2many(
|
||||
comodel_name='account.move',
|
||||
inverse_name='generating_loan_line_id',
|
||||
string='Generated Entries',
|
||||
readonly=True,
|
||||
copy=False,
|
||||
help="Entries that we generated from this loan line"
|
||||
)
|
||||
is_payment_move_posted = fields.Boolean(compute='_compute_is_payment_move_posted')
|
||||
|
||||
@api.depends('principal', 'interest')
|
||||
def _compute_payment(self):
|
||||
for line in self:
|
||||
line.payment = line.principal + line.interest
|
||||
|
||||
@api.depends('loan_id.line_ids', 'loan_id.amount_borrowed', 'principal')
|
||||
def _compute_outstanding_balance(self):
|
||||
for line in self:
|
||||
line.outstanding_balance = line.loan_id.amount_borrowed - sum(line.loan_id.line_ids.filtered(lambda l: line.date and l.date <= line.date).mapped('principal'))
|
||||
|
||||
@api.depends('principal', 'date', 'loan_id.line_ids.date', 'loan_id.line_ids.principal')
|
||||
def _compute_theoretical_balances(self):
|
||||
for line in self:
|
||||
filtered_lines = line.loan_id.line_ids.filtered(lambda l: line.date and l.date and l.date > line.date)
|
||||
line.long_term_theoretical_balance = sum(filtered_lines[12:].mapped('principal'))
|
||||
line.short_term_theoretical_balance = sum(filtered_lines[:12].mapped('principal'))
|
||||
|
||||
@api.depends('loan_id.line_ids', 'date')
|
||||
def _compute_sequence(self):
|
||||
for line in self.sorted('date'):
|
||||
line.sequence = len(line.loan_id.line_ids.filtered(lambda l: line.date and l.date <= line.date))
|
||||
|
||||
@api.depends('generated_move_ids.state')
|
||||
def _compute_is_payment_move_posted(self):
|
||||
for line in self:
|
||||
generated_moves = line.generated_move_ids.filtered(lambda m: m.is_loan_payment_move)
|
||||
# In case of audit trail being activated, we can have more than 1 generated move (i.e. after loan closing/cancellation and re-confirmation),
|
||||
# so we take the one that has no reversal move.
|
||||
if len(generated_moves) > 1 and any(m.reversal_move_ids for m in generated_moves):
|
||||
generated_moves = generated_moves.filtered(lambda m: not m.reversal_move_ids)
|
||||
line.is_payment_move_posted = any(m.state == 'posted' for m in generated_moves)
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
from odoo import models, fields, _
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = "account.move"
|
||||
|
||||
generating_loan_line_id = fields.Many2one(
|
||||
comodel_name='account.loan.line',
|
||||
string='Generating Loan Line',
|
||||
help="Line of the loan that generated this entry",
|
||||
copy=False,
|
||||
readonly=True,
|
||||
index=True,
|
||||
ondelete='cascade',
|
||||
)
|
||||
loan_id = fields.Many2one(related='generating_loan_line_id.loan_id')
|
||||
is_loan_payment_move = fields.Boolean()
|
||||
|
||||
def _post(self, soft=True):
|
||||
posted = super()._post(soft)
|
||||
for move in self:
|
||||
skip_date = move.loan_id.skip_until_date
|
||||
if move.loan_id and all(l.is_payment_move_posted or skip_date and l.date < skip_date for l in move.loan_id.line_ids):
|
||||
move.loan_id.state = 'closed'
|
||||
return posted
|
||||
|
||||
def open_loan(self):
|
||||
self.ensure_one()
|
||||
action = {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _("Original Loan"),
|
||||
'views': [(False, 'form')],
|
||||
'res_model': 'account.loan',
|
||||
'res_id': self.loan_id.id,
|
||||
}
|
||||
return action
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="account_loan_rule" model="ir.rule">
|
||||
<field name="name">Account Loan multi-company</field>
|
||||
<field name="model_id" ref="model_account_loan"/>
|
||||
<field name="domain_force">[('company_id', 'parent_of', company_ids)]</field>
|
||||
</record>
|
||||
|
||||
<record id="account_loan_line_rule" model="ir.rule">
|
||||
<field name="name">Account Loan Line multi-company</field>
|
||||
<field name="model_id" ref="model_account_loan_line"/>
|
||||
<field name="domain_force">[('company_id', 'parent_of', company_ids)]</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
|
||||
"access_account_loan_readonly","access.account.loan","model_account_loan","account.group_account_readonly",1,0,0,0
|
||||
"access_account_loan_write","access.account.loan","model_account_loan","account.group_account_manager",1,1,1,1
|
||||
"access_account_loan_line_readonly","access.account.loan.line","model_account_loan_line","account.group_account_readonly",1,0,0,0
|
||||
"access_account_loan_line_write","access.account.loan.line","model_account_loan_line","account.group_account_manager",1,1,1,1
|
||||
"access_account_loan_close_wizard","access.account.loan.close.wizard","model_account_loan_close_wizard","account.group_account_manager",1,1,1,1
|
||||
"access_account_loan_compute_wizard","access.account.loan.compute.wizard","model_account_loan_compute_wizard","account.group_account_manager",1,1,1,1
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { FileUploader } from "@web/views/fields/file_handler";
|
||||
import { standardWidgetProps } from "@web/views/widgets/standard_widget_props";
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
|
||||
export class NewLoanComponent extends Component {
|
||||
static template = "odex30_account_accountant.NewLoan";
|
||||
static components = {
|
||||
FileUploader,
|
||||
};
|
||||
static props = {
|
||||
...standardWidgetProps,
|
||||
record: { type: Object, optional: true },
|
||||
};
|
||||
|
||||
setup() {
|
||||
this.orm = useService("orm");
|
||||
this.action = useService("action");
|
||||
}
|
||||
|
||||
async onFileUploaded(file) {
|
||||
if (this.props.record && this.props.record.data.name){ //Save the record before calling the wizard
|
||||
await this.props.record.model.root.save({reload: false});
|
||||
}
|
||||
const att_data = {
|
||||
name: file.name,
|
||||
mimetype: file.type,
|
||||
datas: file.data,
|
||||
};
|
||||
const [att_id] = await this.orm.create("ir.attachment", [att_data]);
|
||||
const action = await this.orm.call("account.loan", "action_upload_amortization_schedule", [this.props.record?.resId, att_id]);
|
||||
this.action.doAction(action);
|
||||
}
|
||||
|
||||
async openComputeWizard() {
|
||||
if (this.props.record && this.props.record.data.name){ //Save the record before calling the wizard
|
||||
await this.props.record.model.root.save({reload: false});
|
||||
}
|
||||
const action = await this.orm.call("account.loan", "action_open_compute_wizard", [this.props.record?.resId]);
|
||||
this.action.doAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
export const newLoan = {
|
||||
component: NewLoanComponent,
|
||||
};
|
||||
|
||||
registry.category("view_widgets").add("new_loan", newLoan);
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<templates>
|
||||
<!-- Upload from form view-->
|
||||
<t t-name="odex30_account_accountant.NewLoan">
|
||||
<FileUploader
|
||||
acceptedFileExtensions="props.acceptedFileExtensions"
|
||||
fileUploadClass="'new_loan'"
|
||||
multiUpload="false"
|
||||
onUploaded.bind="onFileUploaded"
|
||||
>
|
||||
<t t-set-slot="toggler">
|
||||
<span groups="account.group_account_invoice">
|
||||
<button class="btn btn-primary">
|
||||
Upload
|
||||
</button>
|
||||
</span>
|
||||
</t>
|
||||
<t t-slot="default"/>
|
||||
</FileUploader>
|
||||
<span class="ms-1" groups="account.group_account_invoice">
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
t-on-click="openComputeWizard"
|
||||
>
|
||||
Compute
|
||||
</button>
|
||||
</span>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import { useState, onWillStart } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { ImportAction } from "@base_import/import_action/import_action";
|
||||
import { BaseImportModel } from "@base_import/import_model";
|
||||
|
||||
|
||||
class AccountLoanImportModel extends BaseImportModel {
|
||||
async init() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class AccountLoanImportAction extends ImportAction {
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
this.action = useService("action");
|
||||
|
||||
this.model = useState(new AccountLoanImportModel({
|
||||
env: this.env,
|
||||
resModel: this.resModel,
|
||||
context: this.props.action.params.context || {},
|
||||
orm: this.orm,
|
||||
}));
|
||||
|
||||
onWillStart(async () => {
|
||||
if (this.props.action.params.context) {
|
||||
this.model.id = this.props.action.params.context.wizard_id;
|
||||
await super.handleFilesUpload([{ name: this.props.action.params.filename }])
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async exit() {
|
||||
if (this.model.resModel === "account.loan.line") {
|
||||
const action = await this.orm.call("account.loan", "action_file_uploaded", [this.model.context.default_loan_id]);
|
||||
return this.action.doAction(action);
|
||||
}
|
||||
super.exit();
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("actions").add("import_loan", AccountLoanImportAction);
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 17 KiB |
|
|
@ -0,0 +1,9 @@
|
|||
.o_view_nocontent {
|
||||
.o_view_nocontent_amortization {
|
||||
@extend %o-nocontent-init-image;
|
||||
width: 600px;
|
||||
height: 300px;
|
||||
background: transparent url(/account_loans/static/src/img/amortization.svg) no-repeat center;
|
||||
background-size: 300px 230px;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import test_loan_management
|
||||
|
|
@ -0,0 +1,443 @@
|
|||
from dateutil.relativedelta import relativedelta
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo import Command, fields
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import file_open
|
||||
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
from odoo.addons.account_loans import _account_loans_add_date_column
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestLoanManagement(AccountTestInvoicingCommon):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.loan_journal = cls.env['account.journal'].search([
|
||||
('company_id', '=', cls.company.id),
|
||||
('type', '=', 'general'),
|
||||
('id', '!=', cls.company.currency_exchange_journal_id.id),
|
||||
], limit=1)
|
||||
cls.long_term_account = cls.env['account.account'].search([
|
||||
('company_ids', '=', cls.company.id),
|
||||
('account_type', '=', 'liability_non_current'),
|
||||
], limit=1)
|
||||
cls.short_term_account = cls.env['account.account'].search([
|
||||
('company_ids', '=', cls.company.id),
|
||||
('account_type', '=', 'liability_current'),
|
||||
], limit=1)
|
||||
cls.expense_account = cls.env['account.account'].search([
|
||||
('company_ids', '=', cls.company.id),
|
||||
('account_type', '=', 'expense'),
|
||||
('id', '!=', cls.company.account_journal_early_pay_discount_loss_account_id.id),
|
||||
], limit=1)
|
||||
|
||||
def create_loan(self, name, date, duration, amount_borrowed, interest, validate=False, skip_until_date=False):
|
||||
loan = self.env['account.loan'].create({
|
||||
'name': name,
|
||||
'date': date,
|
||||
'duration': duration,
|
||||
'amount_borrowed': amount_borrowed,
|
||||
'interest': interest,
|
||||
'skip_until_date': skip_until_date,
|
||||
'journal_id': self.loan_journal.id,
|
||||
'long_term_account_id': self.long_term_account.id,
|
||||
'short_term_account_id': self.short_term_account.id,
|
||||
'expense_account_id': self.expense_account.id,
|
||||
'line_ids': [
|
||||
Command.create({
|
||||
'date': fields.Date.to_date(date) + relativedelta(months=month),
|
||||
'principal': amount_borrowed / duration,
|
||||
'interest': interest / duration,
|
||||
}) for month in range(duration)
|
||||
],
|
||||
})
|
||||
if validate:
|
||||
loan.action_confirm()
|
||||
return loan
|
||||
|
||||
@freeze_time('2024-07-31')
|
||||
def test_loan_values(self):
|
||||
"""Test that the loan values are correctly computed"""
|
||||
# Create the loan
|
||||
loan = self.create_loan('Odoomobile Loan 🚗', '2024-01-01', 2 * 12, 24_000, 2_400, validate=True)
|
||||
|
||||
# Verify that the outstanding balance of the loan is correct
|
||||
self.assertEqual(loan.outstanding_balance, 17_000) # = 24_000 - (1_000 of principal * 7 months (Jan -> July))
|
||||
|
||||
# Verify that the loan lines are correct (computed fields)
|
||||
self.assertEqual(len(loan.line_ids), 24) # 24 months
|
||||
self.assertRecordValues(loan.line_ids[0] | loan.line_ids[4] | loan.line_ids[-1], [{
|
||||
'date': fields.Date.to_date('2024-01-01'),
|
||||
'principal': 1_000,
|
||||
'interest': 100,
|
||||
'payment': 1_100,
|
||||
'outstanding_balance': 23_000, # = 24_000 - 1_000 principal * 1 month
|
||||
}, {
|
||||
'date': fields.Date.to_date('2024-05-01'),
|
||||
'principal': 1_000,
|
||||
'interest': 100,
|
||||
'payment': 1_100,
|
||||
'outstanding_balance': 19_000, # = 24_000 - 1_000 principal * 5 months
|
||||
}, {
|
||||
'date': fields.Date.to_date('2025-12-01'),
|
||||
'principal': 1_000,
|
||||
'interest': 100,
|
||||
'payment': 1_100,
|
||||
'outstanding_balance': 0,
|
||||
}])
|
||||
|
||||
# Verify that the generated moves are correct
|
||||
payment_moves = loan.line_ids.generated_move_ids.filtered(lambda m: len(m.line_ids) == 3) # payments moves have 3 lines (principal + interest = payment)
|
||||
self.assertEqual(len(payment_moves), 24) # 24 months
|
||||
reclassification_moves = (loan.line_ids.generated_move_ids - payment_moves).filtered(lambda m: not m.reversed_entry_id) # reclassification moves have 2 lines (moving principal from one account to another) and have no link to a reversed entry
|
||||
self.assertEqual(len(reclassification_moves), 23) # one less because we have an offset of one month (the first month is already started and should not be reclassified)
|
||||
reclassification_reverse_moves = (loan.line_ids.generated_move_ids - payment_moves) - reclassification_moves
|
||||
self.assertEqual(len(reclassification_reverse_moves), 23) # same
|
||||
|
||||
# Verify that the payment_moves are correct
|
||||
self.assertRecordValues(payment_moves[0] | payment_moves[11] | payment_moves[15] | payment_moves[-1], [{
|
||||
'date': fields.Date.to_date('2024-01-31'),
|
||||
'ref': "Odoomobile Loan 🚗 - Principal & Interest 01/2024",
|
||||
'amount_total': 1_100, # 1_000 principal + 100 interest
|
||||
'generating_loan_line_id': loan.line_ids[0].id,
|
||||
}, {
|
||||
'date': fields.Date.to_date('2024-12-31'),
|
||||
'ref': "Odoomobile Loan 🚗 - Principal & Interest 12/2024",
|
||||
'amount_total': 1_100, # 1_000 principal + 100 interest
|
||||
'generating_loan_line_id': loan.line_ids[11].id,
|
||||
}, {
|
||||
'date': fields.Date.to_date('2025-04-30'),
|
||||
'ref': "Odoomobile Loan 🚗 - Principal & Interest 04/2025",
|
||||
'amount_total': 1_100, # 1_000 principal + 100 interest
|
||||
'generating_loan_line_id': loan.line_ids[15].id,
|
||||
}, {
|
||||
'date': fields.Date.to_date('2025-12-31'),
|
||||
'ref': "Odoomobile Loan 🚗 - Principal & Interest 12/2025",
|
||||
'amount_total': 1_100, # 1_000 principal + 100 interest
|
||||
'generating_loan_line_id': loan.line_ids[-1].id,
|
||||
}])
|
||||
|
||||
self.assertRecordValues(payment_moves[0].line_ids.sorted(lambda l: -l.debit), [{
|
||||
'name': 'Odoomobile Loan 🚗 - Principal 01/2024',
|
||||
'debit': 1_000,
|
||||
'credit': 0,
|
||||
'account_id': self.long_term_account.id,
|
||||
}, {
|
||||
'name': 'Odoomobile Loan 🚗 - Interest 01/2024',
|
||||
'debit': 100,
|
||||
'credit': 0,
|
||||
'account_id': self.expense_account.id,
|
||||
}, {
|
||||
'name': 'Odoomobile Loan 🚗 - Due 01/2024 (Principal $ 1,000.00 + Interest $ 100.00)',
|
||||
'debit': 0,
|
||||
'credit': 1_100,
|
||||
'account_id': self.short_term_account.id,
|
||||
}])
|
||||
|
||||
# Verify that the reclassification moves are correct
|
||||
self.assertRecordValues(reclassification_moves[0] | reclassification_moves[11] | reclassification_moves[15], [{
|
||||
'date': fields.Date.to_date('2024-01-31'),
|
||||
'ref': "Odoomobile Loan 🚗 - Reclassification LT - ST 02/2024 to 01/2025", # offset of 1 month
|
||||
'amount_total': 12_000, # sum of the principals of the next 12 months
|
||||
'generating_loan_line_id': loan.line_ids[0].id,
|
||||
}, {
|
||||
'date': fields.Date.to_date('2024-12-31'),
|
||||
'ref': "Odoomobile Loan 🚗 - Reclassification LT - ST 01/2025 to 12/2025", # offset of 1 month
|
||||
'amount_total': 12_000, # sum of the principals between 01/25 and 12/25
|
||||
'generating_loan_line_id': loan.line_ids[11].id,
|
||||
}, {
|
||||
'date': fields.Date.to_date('2025-04-30'),
|
||||
'ref': "Odoomobile Loan 🚗 - Reclassification LT - ST 05/2025 to 12/2025", # offset of 1 month
|
||||
'amount_total': 8_000, # sum of the principals between 05/25 and 12/25
|
||||
'generating_loan_line_id': loan.line_ids[15].id,
|
||||
}])
|
||||
|
||||
self.assertRecordValues(reclassification_moves[0].line_ids.sorted(lambda l: l.credit), [{
|
||||
'name': f'Odoomobile Loan 🚗 - Reclassification LT - ST 02/2024 to 01/2025 (To {self.short_term_account.code})',
|
||||
'debit': 12_000,
|
||||
'credit': 0,
|
||||
'account_id': self.long_term_account.id,
|
||||
}, {
|
||||
'name': f'Odoomobile Loan 🚗 - Reclassification LT - ST 02/2024 to 01/2025 (From {self.long_term_account.code})',
|
||||
'debit': 0,
|
||||
'credit': 12_000,
|
||||
'account_id': self.short_term_account.id,
|
||||
}])
|
||||
|
||||
# Verify that the reverse reclassification moves are correct
|
||||
self.assertRecordValues(reclassification_reverse_moves[0] | reclassification_reverse_moves[11] | reclassification_reverse_moves[15], [{
|
||||
'date': fields.Date.to_date('2024-02-01'),
|
||||
'ref': "Odoomobile Loan 🚗 - Reversal reclassification LT - ST 02/2024 to 01/2025", # offset of 1 month
|
||||
'amount_total': 12_000, # sum of the principals of the next 12 months
|
||||
'generating_loan_line_id': loan.line_ids[0].id,
|
||||
}, {
|
||||
'date': fields.Date.to_date('2025-01-01'),
|
||||
'ref': "Odoomobile Loan 🚗 - Reversal reclassification LT - ST 01/2025 to 12/2025", # offset of 1 month
|
||||
'amount_total': 12_000, # sum of the principals between 01/25 and 12/25
|
||||
'generating_loan_line_id': loan.line_ids[11].id,
|
||||
}, {
|
||||
'date': fields.Date.to_date('2025-05-01'),
|
||||
'ref': "Odoomobile Loan 🚗 - Reversal reclassification LT - ST 05/2025 to 12/2025", # offset of 1 month
|
||||
'amount_total': 8_000, # sum of the principals between 05/25 and 12/25
|
||||
'generating_loan_line_id': loan.line_ids[15].id,
|
||||
}])
|
||||
|
||||
self.assertRecordValues(reclassification_reverse_moves[0].line_ids.sorted(lambda l: l.debit), [{
|
||||
'name': f'Odoomobile Loan 🚗 - Reversal reclassification LT - ST 02/2024 to 01/2025 (To {self.short_term_account.code})',
|
||||
'credit': 12_000,
|
||||
'debit': 0,
|
||||
'account_id': self.long_term_account.id,
|
||||
}, {
|
||||
'name': f'Odoomobile Loan 🚗 - Reversal reclassification LT - ST 02/2024 to 01/2025 (From {self.long_term_account.code})',
|
||||
'credit': 0,
|
||||
'debit': 12_000,
|
||||
'account_id': self.short_term_account.id,
|
||||
}])
|
||||
|
||||
@freeze_time('2024-07-31')
|
||||
def test_loan_states(self):
|
||||
"""Test the flow of the loan: Draft, Running, Closed, Cancelled"""
|
||||
# Create the loan
|
||||
loan = self.create_loan('Odoomobile Loan 🚗', '2024-01-01', 12, 24_000, 2_400)
|
||||
|
||||
# Verify that the loan is in draft
|
||||
self.assertEqual(loan.state, 'draft')
|
||||
self.assertFalse(loan.line_ids.generated_move_ids)
|
||||
|
||||
# Verify that the loan is running
|
||||
loan.action_confirm()
|
||||
self.assertEqual(loan.state, 'running')
|
||||
self.assertTrue(loan.line_ids.generated_move_ids)
|
||||
self.assertTrue(
|
||||
any(m.state == 'posted' for m in loan.line_ids.generated_move_ids)
|
||||
and
|
||||
any(m.state == 'draft' for m in loan.line_ids.generated_move_ids)
|
||||
) # Mix of draft & posted entries
|
||||
|
||||
# Verify that the loan is cancelled
|
||||
loan.action_cancel()
|
||||
self.assertEqual(loan.state, 'cancelled')
|
||||
self.assertFalse(loan.line_ids.generated_move_ids)
|
||||
|
||||
# Verify that we can reset to draft the loan
|
||||
loan.action_set_to_draft()
|
||||
self.assertEqual(loan.state, 'draft')
|
||||
self.assertFalse(loan.line_ids.generated_move_ids)
|
||||
|
||||
# Run it again
|
||||
loan.action_confirm()
|
||||
self.assertEqual(loan.state, 'running')
|
||||
self.assertTrue(loan.line_ids.generated_move_ids)
|
||||
|
||||
# Close the loan, only draft entries should be removed
|
||||
action = loan.action_close()
|
||||
wizard = self.env[action['res_model']].browse(action['res_id'])
|
||||
wizard.date = fields.Date.to_date('2024-10-31')
|
||||
wizard.action_save()
|
||||
self.assertEqual(loan.state, 'closed')
|
||||
self.assertEqual(len(loan.line_ids.generated_move_ids), 30) # = 24 payment moves + 23 reclassification moves + 23 reversed reclass - (14 + 13 + 13) cancelled moves
|
||||
|
||||
# Reset to draft, the entries should be removed
|
||||
loan.action_set_to_draft()
|
||||
self.assertEqual(loan.state, 'draft')
|
||||
self.assertFalse(loan.line_ids.generated_move_ids)
|
||||
|
||||
# Create a new loan that should be automatically closed when the last generated move is posted
|
||||
loan2 = self.create_loan('Odoomobile Loan 🚗', '2024-01-01', 12, 24_000, 2_400, validate=True)
|
||||
self.assertEqual(loan2.state, 'running')
|
||||
with freeze_time('2024-12-31'):
|
||||
self.env.ref('account.ir_cron_auto_post_draft_entry').method_direct_trigger()
|
||||
self.assertEqual(loan2.state, 'closed')
|
||||
|
||||
@freeze_time('2024-06-23')
|
||||
def test_loan_states_with_audit_trail(self):
|
||||
"""Test the flow of the loan: Draft, Running, Closed, Cancelled"""
|
||||
self.company.check_account_audit_trail = True
|
||||
# Create the loan
|
||||
loan = self.create_loan('Odoomobile Loan 🚗', '2024-01-01', 12, 24_000, 2_400)
|
||||
|
||||
# Verify that the loan is in draft
|
||||
self.assertEqual(loan.state, 'draft')
|
||||
self.assertFalse(loan.line_ids.generated_move_ids)
|
||||
|
||||
# Verify that the loan is running
|
||||
loan.action_confirm()
|
||||
self.assertEqual(loan.state, 'running')
|
||||
self.assertEqual(len(loan.line_ids.generated_move_ids.filtered(lambda m: m.state == 'posted')), 15)
|
||||
self.assertEqual(len(loan.line_ids.generated_move_ids), 34)
|
||||
|
||||
# Verify that the loan is cancelled
|
||||
loan.action_cancel()
|
||||
self.assertEqual(loan.state, 'cancelled')
|
||||
self.assertFalse(len(loan.line_ids.generated_move_ids.filtered(lambda m: m.state == 'posted')))
|
||||
self.assertEqual(len(loan.line_ids.generated_move_ids), 15)
|
||||
|
||||
# Verify that we can reset to draft the loan
|
||||
loan.action_set_to_draft()
|
||||
self.assertEqual(loan.state, 'draft')
|
||||
self.assertEqual(len(loan.line_ids.generated_move_ids), 15)
|
||||
|
||||
# Run it again
|
||||
loan.action_confirm()
|
||||
self.assertEqual(loan.state, 'running')
|
||||
self.assertEqual(len(loan.line_ids.generated_move_ids.filtered(lambda m: m.state == 'posted')), 15)
|
||||
self.assertEqual(len(loan.line_ids.generated_move_ids), 49)
|
||||
|
||||
# Close the loan, only draft entries should be removed
|
||||
action = loan.action_close()
|
||||
wizard = self.env[action['res_model']].browse(action['res_id'])
|
||||
wizard.action_save()
|
||||
self.assertEqual(loan.state, 'closed')
|
||||
self.assertEqual(len(loan.line_ids.generated_move_ids.filtered(lambda m: m.state == 'posted')), 15)
|
||||
self.assertEqual(len(loan.line_ids.generated_move_ids), 33)
|
||||
|
||||
# Reset to draft, the entries should be removed
|
||||
loan.action_set_to_draft()
|
||||
self.assertEqual(loan.state, 'draft')
|
||||
self.assertFalse(len(loan.line_ids.generated_move_ids.filtered(lambda m: m.state == 'posted')))
|
||||
self.assertEqual(len(loan.line_ids.generated_move_ids), 30)
|
||||
|
||||
@freeze_time('2024-01-01')
|
||||
def test_loan_import_amortization_schedule(self):
|
||||
"""Test that we can import an amortization schedule from a file"""
|
||||
# Upload the file from the List View -> Create a new Loan
|
||||
with file_open('account_loans/demo/files/loan_amortization_demo.csv', 'rb') as f:
|
||||
attachment = self.env['ir.attachment'].create({
|
||||
'name': 'loan_amortization_demo.csv',
|
||||
'raw': f.read(),
|
||||
})
|
||||
attachment = _account_loans_add_date_column(attachment) # fill the date column from -1 year to +3 years
|
||||
|
||||
action = self.env['account.loan'].action_upload_amortization_schedule(attachment.id)
|
||||
loan = self.env['account.loan'].browse(action.get('params', {}).get('context', {}).get('default_loan_id'))
|
||||
import_wizard = self.env['base_import.import'].browse(action.get('params', {}).get('context', {}).get('wizard_id'))
|
||||
result = import_wizard.parse_preview({
|
||||
'quoting': '"',
|
||||
'separator': ',',
|
||||
'date_format': '%Y-%m-%d',
|
||||
'has_headers': True,
|
||||
})
|
||||
import_wizard.with_context(default_loan_id=loan.id).execute_import(
|
||||
['date', 'principal', 'interest'],
|
||||
[],
|
||||
result["options"],
|
||||
)
|
||||
loan.action_file_uploaded()
|
||||
loan.write({
|
||||
'journal_id': self.loan_journal.id,
|
||||
'long_term_account_id': self.long_term_account.id,
|
||||
'short_term_account_id': self.short_term_account.id,
|
||||
'expense_account_id': self.expense_account.id,
|
||||
})
|
||||
loan.action_confirm()
|
||||
|
||||
self.assertRecordValues(loan, [{
|
||||
'date': fields.Date.from_string('2023-01-01'),
|
||||
'state': 'running',
|
||||
'name': attachment.name,
|
||||
'amount_borrowed': 19_900.25, # Sum of principals
|
||||
'outstanding_balance': 15_981.65, # = 19_900.25 - principal of first 12 lines
|
||||
}])
|
||||
|
||||
self.assertEqual(len(loan.line_ids), 48) # 4 years
|
||||
self.assertRecordValues(loan.line_ids[0] | loan.line_ids[-1], [{
|
||||
'date': fields.Date.from_string('2023-01-01'),
|
||||
'principal': 304.60,
|
||||
'interest': 250.00,
|
||||
'payment': 554.6,
|
||||
'outstanding_balance': 19_595.65, # = 19_900.25 - 304.60
|
||||
}, {
|
||||
'date': fields.Date.from_string('2026-12-01'),
|
||||
'principal': 547.72,
|
||||
'interest': 6.88,
|
||||
'payment': 554.6,
|
||||
'outstanding_balance': 0,
|
||||
}])
|
||||
|
||||
# Upload the file from the Form View -> Update the current Loan
|
||||
loan2 = self.create_loan('Loan 2', '2024-01-01', 2 * 12, 24_000, 2_400, validate=True)
|
||||
self.assertEqual(len(loan2.line_ids), 24)
|
||||
|
||||
# Override all previous lines, and recompute amount_borrowed, date, ...
|
||||
action = loan2.action_upload_amortization_schedule(attachment.id)
|
||||
self.assertEqual(action.get('params', {}).get('context', {}).get('default_loan_id'), loan2.id)
|
||||
import_wizard = self.env['base_import.import'].browse(action.get('params', {}).get('context', {}).get('wizard_id'))
|
||||
result = import_wizard.parse_preview({
|
||||
'quoting': '"',
|
||||
'separator': ',',
|
||||
'date_format': '%Y-%m-%d',
|
||||
'has_headers': True,
|
||||
})
|
||||
import_wizard.with_context(default_loan_id=loan2.id).execute_import(
|
||||
['date', 'principal', 'interest'],
|
||||
[],
|
||||
result["options"],
|
||||
)
|
||||
loan2.action_file_uploaded()
|
||||
loan2.action_confirm()
|
||||
|
||||
self.assertRecordValues(loan, [{
|
||||
'date': fields.Date.from_string('2023-01-01'),
|
||||
'state': 'running',
|
||||
'name': attachment.name,
|
||||
'amount_borrowed': 19_900.25, # Sum of principals
|
||||
'outstanding_balance': 15_981.65, # = 19_900.25 - principal of first 12 lines
|
||||
}])
|
||||
|
||||
self.assertEqual(len(loan.line_ids), 48) # 4 years
|
||||
self.assertRecordValues(loan.line_ids[0] | loan.line_ids[-1], [{
|
||||
'date': fields.Date.from_string('2023-01-01'),
|
||||
'principal': 304.60,
|
||||
'interest': 250.00,
|
||||
'payment': 554.6,
|
||||
'outstanding_balance': 19_595.65, # = 19_900.25 - 304.60
|
||||
}, {
|
||||
'date': fields.Date.from_string('2026-12-01'),
|
||||
'principal': 547.72,
|
||||
'interest': 6.88,
|
||||
'payment': 554.6,
|
||||
'outstanding_balance': 0,
|
||||
}])
|
||||
|
||||
def test_loan_zero_interest(self):
|
||||
loan = self.env['account.loan'].create({'name': '0 interest loan', 'date': '2024-01-01', 'amount_borrowed': 24_000})
|
||||
wizard = self.env['account.loan.compute.wizard'].browse(loan.action_open_compute_wizard()['res_id'])
|
||||
wizard.interest_rate = 0
|
||||
wizard.action_save()
|
||||
self.assertEqual(len(loan.line_ids), 12) # default loan term is 1 year = 12 months
|
||||
self.assertTrue(all(payment == 2000 for payment in loan.line_ids.mapped('payment'))) # 24,000 / 12 months = 2,000/month
|
||||
|
||||
@freeze_time('2024-07-31')
|
||||
def test_loan_skip_until_date(self):
|
||||
"""Test the skip_until_date field"""
|
||||
loan = self.create_loan('Odoomobile Loan 🚗', '2024-01-01', 12, 24_000, 2_400, validate=True, skip_until_date='2024-05-15')
|
||||
|
||||
self.assertEqual(loan.state, 'running')
|
||||
self.assertTrue(loan.line_ids.generated_move_ids)
|
||||
# Outstanding balance should be 24_000 - 2_000 * 7 months (Jan -> July), including skipped period
|
||||
self.assertEqual(loan.outstanding_balance, 10_000)
|
||||
|
||||
@freeze_time('2025-01-01')
|
||||
def test_loan_skip_until_date_2(self):
|
||||
"""Test loan closing when skip_until_date field is set"""
|
||||
loan = self.env['account.loan'].create({
|
||||
'name': 'Test',
|
||||
'date': '2024-01-01',
|
||||
'duration': 12,
|
||||
'amount_borrowed': 20_000,
|
||||
'interest': 107.87,
|
||||
'skip_until_date': '2024-10-31',
|
||||
'journal_id': self.loan_journal.id,
|
||||
'long_term_account_id': self.long_term_account.id,
|
||||
'short_term_account_id': self.short_term_account.id,
|
||||
'expense_account_id': self.expense_account.id,
|
||||
})
|
||||
|
||||
wizard = self.env['account.loan.compute.wizard'].browse(loan.action_open_compute_wizard()['res_id'])
|
||||
wizard.action_save()
|
||||
loan.action_confirm()
|
||||
|
||||
self.assertTrue(loan.line_ids.generated_move_ids)
|
||||
self.assertEqual(loan.state, 'closed')
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<odoo>
|
||||
<record id="asset_group_form_view_inherit_loan" model="ir.ui.view">
|
||||
<field name="name">account.asset.group.form</field>
|
||||
<field name="model">account.asset.group</field>
|
||||
<field name="inherit_id" ref="odex30_account_asset.asset_group_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='button_box']" position="inside">
|
||||
<button name="action_open_linked_loans"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-bars"
|
||||
invisible="count_linked_loans == 0">
|
||||
<field string="Loan(s)" name="count_linked_loans" widget="statinfo"/>
|
||||
</button>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<odoo>
|
||||
<record id="view_account_asset_form_inherit_loan" model="ir.ui.view">
|
||||
<field name="name">account.asset.form</field>
|
||||
<field name="model">account.asset</field>
|
||||
<field name="inherit_id" ref="odex30_account_asset.view_account_asset_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='button_box']" position="inside">
|
||||
<button name="action_open_linked_loans"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-bars"
|
||||
invisible="count_linked_loans == 0">
|
||||
<field string="Related Loan(s)" name="count_linked_loans" widget="statinfo"/>
|
||||
</button>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,332 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<!-- ACCOUNT LOAN-->
|
||||
|
||||
<record id="account_loan_form_view" model="ir.ui.view">
|
||||
<field name="name">account.loan.form</field>
|
||||
<field name="model">account.loan</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Loan">
|
||||
<header>
|
||||
<button
|
||||
name="action_confirm" string="Confirm" class="btn btn-primary"
|
||||
type="object" groups="account.group_account_invoice"
|
||||
invisible="state != 'draft'"
|
||||
/>
|
||||
<widget name="new_loan" invisible="line_ids"/>
|
||||
<button
|
||||
name="action_reset" string="Reset" class="btn btn-secondary"
|
||||
type="object" groups="account.group_account_manager"
|
||||
invisible="state != 'draft' or not line_ids"
|
||||
/>
|
||||
<button
|
||||
name="action_close" string="Close" class="btn btn-secondary"
|
||||
type="object" groups="account.group_account_manager"
|
||||
invisible="state != 'running'"
|
||||
/>
|
||||
<button
|
||||
name="action_set_to_draft" string="Set to Draft" class="btn btn-secondary"
|
||||
type="object" groups="account.group_account_manager"
|
||||
invisible="state in ('draft', 'running')"
|
||||
/>
|
||||
<button
|
||||
name="action_cancel" string="Cancel" class="btn btn-secondary"
|
||||
type="object" groups="account.group_account_manager"
|
||||
invisible="state != 'running'"
|
||||
/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,running,closed"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button
|
||||
class="oe_stat_button"
|
||||
icon="fa-bars"
|
||||
type="object"
|
||||
name="action_open_loan_entries"
|
||||
invisible="state == 'draft'"
|
||||
>
|
||||
<field string="Posted Entries" name="nb_posted_entries" widget="statinfo"/>
|
||||
</button>
|
||||
<button
|
||||
class="oe_stat_button"
|
||||
icon="fa-bars"
|
||||
type="object"
|
||||
name="action_open_linked_assets"
|
||||
invisible="count_linked_assets == 0"
|
||||
>
|
||||
<field string="Related Asset(s)" name="count_linked_assets" widget="statinfo"/>
|
||||
</button>
|
||||
</div>
|
||||
<field name="company_id" invisible="1"/> <!-- Needed so the company_id is fetched when creating a new record -->
|
||||
<field name="currency_id" invisible="1"/> <!-- Needed for monetary widget -->
|
||||
<div class="oe_title">
|
||||
<label for="name"/>
|
||||
<h1>
|
||||
<field name="name" placeholder="Loan name"/>
|
||||
</h1>
|
||||
<group>
|
||||
<group>
|
||||
<field name="amount_borrowed" widget="monetary" decoration-danger="amount_borrowed_difference != 0" readonly="state != 'draft'"/>
|
||||
<field name="interest" widget="monetary" decoration-danger="interest_difference != 0" readonly="state != 'draft'"/>
|
||||
<field name="outstanding_balance" widget="monetary"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="date" decoration-danger="is_wrong_date" readonly="state != 'draft'"/>
|
||||
<label for="duration" string="Duration"/>
|
||||
<div>
|
||||
<field name="duration" class="oe_inline" decoration-danger="duration_difference != 0" readonly="state != 'draft'"/>
|
||||
<span class="oe_inline" invisible="duration == 1">months</span>
|
||||
<span class="oe_inline" invisible="duration != 1">month</span>
|
||||
</div>
|
||||
<field name="asset_group_id"/>
|
||||
</group>
|
||||
</group>
|
||||
</div>
|
||||
<field name="loan_properties" columns="2"/>
|
||||
<notebook>
|
||||
<page string="Amortization schedule" name="amortization_schedule">
|
||||
<field name="line_ids" readonly="state != 'draft'"/>
|
||||
</page>
|
||||
<page string="Loan Settings" name="main_page">
|
||||
<group>
|
||||
<group name="loan_settings_column1">
|
||||
<field name="long_term_account_id" readonly="state != 'draft'"/>
|
||||
<field name="short_term_account_id" readonly="state != 'draft'"/>
|
||||
<field name="expense_account_id" readonly="state != 'draft'"/>
|
||||
<field name="journal_id" readonly="state != 'draft'"/>
|
||||
</group>
|
||||
<group name="loan_settings_column2">
|
||||
<field name="skip_until_date" readonly="state != 'draft'" placeholder="1st amortization schedule"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="account_loan_list_view" model="ir.ui.view">
|
||||
<field name="name">account.loan.list</field>
|
||||
<field name="model">account.loan</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Loans"
|
||||
decoration-info="state == 'draft'"
|
||||
decoration-muted="state in ('closed', 'cancelled')"
|
||||
>
|
||||
<field name="currency_id" column_invisible="1"/> <!-- Needed for monetary widget -->
|
||||
<field name="name"/>
|
||||
<field name="date" string="Start Date"/>
|
||||
<field name="end_date"/>
|
||||
<field name="amount_borrowed" sum="Total Amounts Borrowed"/>
|
||||
<field name="outstanding_balance" sum="Total Outstanding Balance"/>
|
||||
<field name="state" widget="badge" decoration-info="state == 'draft'" decoration-success="state == 'running'" decoration-danger="state in ('closed', 'cancelled')"/>
|
||||
<field name="long_term_account_id" optional="hide"/>
|
||||
<field name="short_term_account_id" optional="hide"/>
|
||||
<field name="expense_account_id" optional="hide"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="account_loan_search_view" model="ir.ui.view">
|
||||
<field name="name">account.loan.search</field>
|
||||
<field name="model">account.loan</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Loan">
|
||||
<field name="name" string="Loan" filter_domain="['|', ('name', 'ilike', self), ('display_name', 'ilike', self)]"/>
|
||||
<field name="asset_group_id"/>
|
||||
<field name="loan_properties"/>
|
||||
<filter string="Current" name="current" domain="[('state', 'in', ('draft', 'running'))]" help="Draft & Running Loans"/>
|
||||
<filter string="Closed" name="closed" domain="[('state', '=', 'closed')]" help="Closed Loans"/>
|
||||
<separator/>
|
||||
<filter string="Date" name="date" date="date"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Asset Group" name="group_by_asset_group" domain="[]" context="{'group_by': 'asset_group_id'}"/>
|
||||
<filter string="Properties" name="group_by_properties" domain="[]" context="{'group_by': 'loan_properties'}"/>
|
||||
<filter string="Date" name="group_by_date" domain="[]" context="{'group_by': 'date'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="account_loan_kanban_view" model="ir.ui.view">
|
||||
<field name="name">account.loan.search</field>
|
||||
<field name="model">account.loan</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_kanban_mobile" sample="1">
|
||||
<field name="currency_id"/>
|
||||
<templates>
|
||||
<t t-name="card">
|
||||
<div class="row mb4">
|
||||
<field name="name" class="fw-bold fs-5 col-10"/>
|
||||
<div class="col-2">
|
||||
<field name="state" class="float-end" widget="label_selection" options="{'classes': {'draft': 'primary', 'running': 'success', 'closed': 'default'}}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-5">
|
||||
<field name="amount_borrowed" widget='monetary'/>
|
||||
</div>
|
||||
<div class="col-7 text-end">
|
||||
<field name="date"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="account_loan_pivot_view" model="ir.ui.view">
|
||||
<field name="name">account.loan.pivot</field>
|
||||
<field name="model">account.loan</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot sample="1">
|
||||
<field name="date" interval="year" type="col"/>
|
||||
<field name="name" type="row"/>
|
||||
<field name="amount_borrowed" type="measure"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="account_loan_graph_view" model="ir.ui.view">
|
||||
<field name="name">account.loan.graph</field>
|
||||
<field name="model">account.loan</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph type="line" sample="1">
|
||||
<field name="date" interval="year"/>
|
||||
<field name="name"/>
|
||||
<field name="amount_borrowed" type="measure"/>
|
||||
<field name="interest" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ACCOUNT LOAN LINE-->
|
||||
|
||||
<record id="account_loan_line_list_view" model="ir.ui.view">
|
||||
<field name="name">account.loan.line.list</field>
|
||||
<field name="model">account.loan.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Loan lines" editable="bottom" delete="1" limit="80" decoration-info="not is_payment_move_posted">
|
||||
<field name="currency_id" column_invisible="1"/> <!-- Needed for monetary widget -->
|
||||
<field name="sequence" width="30px" optional="hide"/>
|
||||
<field name="date"/>
|
||||
<field name="principal" string="Principals" sum="Total principals"/>
|
||||
<field name="interest" string="Interests" sum="Total interests"/>
|
||||
<field name="payment" string="Payments" sum="Total payments"/>
|
||||
<field name="outstanding_balance"/>
|
||||
<field name="long_term_theoretical_balance" optional="hide"/>
|
||||
<field name="short_term_theoretical_balance" optional="hide"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="account_loan_line_search_view" model="ir.ui.view">
|
||||
<field name="name">account.loan.line.search</field>
|
||||
<field name="model">account.loan.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Loan">
|
||||
<field name="loan_name" string="Loan" filter_domain="[('loan_name', 'ilike', self)]"/>
|
||||
<filter string="Current" name="current" domain="[('loan_state', 'in', ('draft', 'running'))]" help="Draft & Running Loans"/>
|
||||
<filter string="Closed" name="closed" domain="[('loan_state', '=', 'closed')]" help="Closed Loans"/>
|
||||
<separator/>
|
||||
<filter string="Loan Date" name="loan_line_date" date="date"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Loan" name="group_by_loan" domain="[]" context="{'group_by': 'loan_id'}"/>
|
||||
<filter string="Loan Date" name="group_by_loan_line_date" domain="[]" context="{'group_by': 'date'}"/>
|
||||
<filter string="Asset Group" name="group_by_asset_group" domain="[]" context="{'group_by': 'loan_asset_group_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="account_loan_line_pivot_view" model="ir.ui.view">
|
||||
<field name="name">account.loan.line.pivot</field>
|
||||
<field name="model">account.loan.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Loans Analysis" sample="1">
|
||||
<field name="date" interval="year" type="col"/>
|
||||
<field name="loan_id" type="row"/>
|
||||
<field name="principal" type="measure"/>
|
||||
<field name="interest" type="measure"/>
|
||||
<field name="payment" type="measure"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ACCOUNT MOVE LIST VIEW-->
|
||||
|
||||
<record id="account_loan_view_account_move_list_view" model="ir.ui.view">
|
||||
<field name="name">account.move.list</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account.view_move_tree"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//list" position="attributes">
|
||||
<attribute name="default_order">date,name,ref</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ACTIONS -->
|
||||
|
||||
<record id="action_view_account_loans" model="ir.actions.act_window">
|
||||
<field name="name">Loans</field>
|
||||
<field name="path">loans</field>
|
||||
<field name="res_model">account.loan</field>
|
||||
<field name="search_view_id" ref="account_loan_search_view"/>
|
||||
<field name="context">{'search_default_current':1}</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0),
|
||||
(0, 0, {'view_mode': 'list', 'view_id': ref('account_loan_list_view')}),
|
||||
(0, 0, {'view_mode': 'form', 'view_id': ref('account_loan_form_view')}),
|
||||
(0, 0, {'view_mode': 'kanban', 'view_id': ref('account_loan_kanban_view')}),
|
||||
(0, 0, {'view_mode': 'pivot', 'view_id': ref('account_loan_pivot_view')}),
|
||||
(0, 0, {'view_mode': 'graph', 'view_id': ref('account_loan_graph_view')}),
|
||||
]"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_amortization"/>
|
||||
<h2>
|
||||
Manage Your Acquired Loans with Automated Adjustments.
|
||||
</h2>
|
||||
<p>
|
||||
Set up your amortization schedule, or import it, and let Odoo handle the monthly interest and principal adjustments automatically.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_account_loans_analysis" model="ir.actions.act_window">
|
||||
<field name="name">Loans Analysis</field>
|
||||
<field name="path">loans-analysis</field>
|
||||
<field name="res_model">account.loan.line</field>
|
||||
<field name="search_view_id" ref="account_loan_line_search_view"/>
|
||||
<field name="context">{'search_default_current':1}</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0),
|
||||
(0, 0, {'view_mode': 'pivot', 'view_id': ref('account_loan_line_pivot_view')}),
|
||||
]"/>
|
||||
</record>
|
||||
|
||||
<!-- MENU ITEMS -->
|
||||
|
||||
<menuitem
|
||||
id="menu_action_loans"
|
||||
sequence="62"
|
||||
parent="account.menu_finance_entries"
|
||||
action="action_view_account_loans"
|
||||
groups="account.group_account_readonly"
|
||||
/>
|
||||
|
||||
<menuitem
|
||||
id="menu_action_loans_analysis"
|
||||
name="Loans Analysis"
|
||||
action="action_view_account_loans_analysis"
|
||||
parent="account.account_reports_management_menu"
|
||||
groups="account.group_account_readonly"
|
||||
/>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<odoo>
|
||||
<record id="view_move_form_inherit_loan" model="ir.ui.view">
|
||||
<field name="name">account.move.form.inherit</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="odex30_account_accountant.view_move_form_inherit"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='button_box']" position="inside">
|
||||
<button
|
||||
name="open_loan"
|
||||
class="oe_stat_button"
|
||||
icon="fa-bars"
|
||||
type="object"
|
||||
invisible="not generating_loan_line_id"
|
||||
string="Related Loan"
|
||||
>
|
||||
</button>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
from . import account_loan_compute_wizard
|
||||
from . import account_loan_close_wizard
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
from odoo import models, fields, _
|
||||
from odoo.tools import format_date
|
||||
|
||||
|
||||
class AccountCloseWizard(models.TransientModel):
|
||||
_name = 'account.loan.close.wizard'
|
||||
_description = 'Close Loan Wizard'
|
||||
|
||||
loan_id = fields.Many2one(
|
||||
comodel_name='account.loan',
|
||||
string='Loan',
|
||||
required=True,
|
||||
)
|
||||
date = fields.Date(
|
||||
string='Close Date',
|
||||
default=fields.Date.context_today,
|
||||
required=True,
|
||||
)
|
||||
|
||||
def action_save(self):
|
||||
self.loan_id.line_ids.generated_move_ids.filtered(lambda m: m.generating_loan_line_id.date > self.date and m.state == 'draft').unlink()
|
||||
self.loan_id.state = 'closed'
|
||||
self.loan_id.message_post(body=_("Closed on the %(date)s", date=format_date(self.env, self.date)))
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="view_account_loan_close_wizard" model="ir.ui.view">
|
||||
<field name="name">account.loan.close.wizard.form</field>
|
||||
<field name="model">account.loan.close.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
All draft entries after the
|
||||
<field name="date" class="oe-inline col-1"/>
|
||||
will be deleted and the loan will be marked as closed.
|
||||
</sheet>
|
||||
<footer>
|
||||
<button string="Apply" name="action_save" type="object" class="btn-primary" data-hotkey="q"/>
|
||||
<button string="Discard" class="btn-secondary" special="cancel" data-hotkey="x"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_account_loan_close_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Close Loan Wizard</field>
|
||||
<field name="res_model">account.loan.close.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="view_account_loan_close_wizard"/>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import models, fields, _, api
|
||||
from odoo.tools.misc import format_date
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
from ..lib import pyloan
|
||||
|
||||
|
||||
class AccountLoanComputeWizard(models.TransientModel):
|
||||
_name = 'account.loan.compute.wizard'
|
||||
_description = 'Loan Compute Wizard'
|
||||
|
||||
loan_id = fields.Many2one(
|
||||
comodel_name='account.loan',
|
||||
string='Loan',
|
||||
required=True,
|
||||
)
|
||||
currency_id = fields.Many2one(related='loan_id.currency_id')
|
||||
loan_amount = fields.Monetary(
|
||||
string='Loan Amount',
|
||||
required=True,
|
||||
)
|
||||
interest_rate = fields.Float(
|
||||
string='Interest Rate',
|
||||
default=1.0,
|
||||
required=True,
|
||||
)
|
||||
loan_term = fields.Integer(
|
||||
string='Loan Term',
|
||||
default=1,
|
||||
required=True,
|
||||
)
|
||||
start_date = fields.Date(
|
||||
string='Start Date',
|
||||
required=True,
|
||||
default=fields.Date.context_today,
|
||||
)
|
||||
first_payment_date = fields.Date(
|
||||
string='First Payment',
|
||||
required=True,
|
||||
default=lambda self: fields.Date.context_today(self).replace(day=1) + relativedelta(months=1), # first day of next month
|
||||
)
|
||||
payment_end_of_month = fields.Selection(
|
||||
string='Payment',
|
||||
selection=[
|
||||
('end_of_month', 'End of Month'),
|
||||
('at_anniversary', 'At Anniversary'),
|
||||
],
|
||||
default='end_of_month',
|
||||
required=True,
|
||||
)
|
||||
compounding_method = fields.Selection(
|
||||
string='Compounding Method',
|
||||
selection=[
|
||||
('30A/360', '30A/360'),
|
||||
('30U/360', '30U/360'),
|
||||
('30E/360', '30E/360'),
|
||||
('30E/360 ISDA', '30E/360 ISDA'),
|
||||
('A/360', 'A/360'),
|
||||
('A/365F', 'A/365F'),
|
||||
('A/A ISDA', 'A/A ISDA'),
|
||||
('A/A AFB', 'A/A AFB'),
|
||||
],
|
||||
default='30E/360',
|
||||
required=True,
|
||||
)
|
||||
preview = fields.Text(compute='_compute_preview')
|
||||
|
||||
# Onchange
|
||||
@api.onchange('loan_amount', 'interest_rate', 'loan_term', 'start_date', 'first_payment_date')
|
||||
def _onchange_preview(self):
|
||||
if self.loan_amount < 0:
|
||||
raise ValidationError(_("Loan Amount must be positive"))
|
||||
if self.interest_rate < 0 or self.interest_rate > 100:
|
||||
raise ValidationError(_("Interest Rate must be between 0 and 100"))
|
||||
if self.loan_term < 0:
|
||||
raise ValidationError(_("Loan Term must be positive"))
|
||||
if self.first_payment_date and self.start_date and self.start_date + relativedelta(years=self.loan_term) < self.first_payment_date:
|
||||
raise ValidationError(_("The First Payment Date must be before the end of the loan."))
|
||||
|
||||
@api.onchange('start_date')
|
||||
def _onchange_start_date(self):
|
||||
self.first_payment_date = self.start_date and self.start_date.replace(day=1) + relativedelta(months=1) # first day of next month
|
||||
|
||||
# Compute
|
||||
def _get_loan_payment_schedule(self):
|
||||
self.ensure_one()
|
||||
loan = pyloan.Loan(
|
||||
loan_amount=self.loan_amount,
|
||||
interest_rate=self.interest_rate,
|
||||
loan_term=self.loan_term,
|
||||
start_date=format_date(self.env, self.start_date, date_format='yyyy-MM-dd'),
|
||||
first_payment_date=format_date(self.env, self.first_payment_date, date_format='yyyy-MM-dd') if self.first_payment_date and self.payment_end_of_month == 'at_anniversary' else None,
|
||||
payment_end_of_month=self.payment_end_of_month == 'end_of_month',
|
||||
compounding_method=self.compounding_method,
|
||||
loan_type='annuity' if self.interest_rate else 'linear',
|
||||
)
|
||||
if schedule := loan.get_payment_schedule():
|
||||
return schedule[1:] # Skip first line which is always 0 (simply the start of the loan)
|
||||
return []
|
||||
|
||||
@api.depends('loan_amount', 'interest_rate', 'loan_term', 'start_date', 'first_payment_date', 'payment_end_of_month', 'compounding_method')
|
||||
def _compute_preview(self):
|
||||
def get_preview_row(payment):
|
||||
return (
|
||||
f"{format_date(self.env, payment.date): <12} "
|
||||
f"{wizard.currency_id.format(float(payment.principal_amount)):>15} "
|
||||
f"{wizard.currency_id.format(float(payment.interest_amount)):>15} "
|
||||
f"{wizard.currency_id.format(float(payment.payment_amount)):>15} "
|
||||
f"{wizard.currency_id.format(float(payment.loan_balance_amount)):>15}\n"
|
||||
)
|
||||
for wizard in self:
|
||||
if wizard.loan_amount and wizard.loan_term and wizard.start_date:
|
||||
schedule = self._get_loan_payment_schedule()
|
||||
if not schedule:
|
||||
wizard.preview = ''
|
||||
continue
|
||||
preview = "{: <12} {:>15} {:>15} {:>15} {:>15}\n".format(_('Date'), _('Principal'), _('Interest'), _('Payment'), _('Balance'))
|
||||
for payment in schedule[:5]:
|
||||
preview += get_preview_row(payment)
|
||||
preview += "{: <12} {:>15} {:>15} {:>15} {:>15}\n".format("...", "...", "...", "...", "...")
|
||||
for payment in schedule[-5:]:
|
||||
preview += get_preview_row(payment)
|
||||
wizard.preview = preview
|
||||
else:
|
||||
wizard.preview = ''
|
||||
|
||||
# Actions
|
||||
def action_save(self):
|
||||
loan_lines_values = []
|
||||
for payment in self._get_loan_payment_schedule():
|
||||
loan_lines_values.append({
|
||||
'loan_id': self.loan_id.id,
|
||||
'date': payment.date,
|
||||
'principal': float(payment.principal_amount),
|
||||
'interest': float(payment.interest_amount),
|
||||
})
|
||||
self.env['account.loan.line'].create(loan_lines_values)
|
||||
self.loan_id.write({
|
||||
'date': self.start_date,
|
||||
'amount_borrowed': self.loan_amount,
|
||||
'interest': sum(self.loan_id.line_ids.mapped('interest')),
|
||||
'duration': len(self.loan_id.line_ids),
|
||||
})
|
||||
return {
|
||||
'name': self.loan_id.name,
|
||||
'res_id': self.loan_id.id,
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': self.loan_id._name,
|
||||
'target': 'self',
|
||||
'views': [[False, 'form']],
|
||||
'context': self.env.context,
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="view_account_loan_compute_wizard" model="ir.ui.view">
|
||||
<field name="name">account.loan.compute.wizard.form</field>
|
||||
<field name="model">account.loan.compute.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<field name="currency_id" invisible="1"/> <!-- Needed for monetary widget -->
|
||||
<group>
|
||||
<group name="loan_values">
|
||||
<field name="loan_amount" widget="monetary"/>
|
||||
<label for="interest_rate" string="Interest Rate"/>
|
||||
<div>
|
||||
<field name="interest_rate" class="oe_inline"/>
|
||||
<span class="oe_inline">%</span>
|
||||
</div>
|
||||
<label for="loan_term" string="Loan Term"/>
|
||||
<div>
|
||||
<field name="loan_term" class="oe_inline"/>
|
||||
<span class="oe_inline" invisible="loan_term == 1">years</span>
|
||||
<span class="oe_inline" invisible="loan_term != 1">year</span>
|
||||
</div>
|
||||
</group>
|
||||
<group name="loan_dates">
|
||||
<field name="start_date"/>
|
||||
<field name="payment_end_of_month"/>
|
||||
<field name="first_payment_date" invisible="payment_end_of_month == 'end_of_month'"/>
|
||||
<field name="compounding_method"/>
|
||||
</group>
|
||||
</group>
|
||||
<label for="preview" string="Preview" invisible="not preview"/>
|
||||
<pre invisible="not preview"><field name="preview"/></pre>
|
||||
</sheet>
|
||||
<footer>
|
||||
<button string="Apply" name="action_save" type="object" class="btn-primary" data-hotkey="q"/>
|
||||
<button string="Discard" class="btn-secondary" special="cancel" data-hotkey="x"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_account_loan_compute_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Loan Compute Wizard</field>
|
||||
<field name="res_model">account.loan.compute.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="view_account_loan_compute_wizard"/>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -2022,7 +2022,7 @@ class TestBankRecWidget(TestBankRecWidgetCommon):
|
|||
|
||||
def test_auto_reconcile_cron(self):
|
||||
self.env['account.reconcile.model'].search([('company_id', '=', self.company_data['company'].id)]).unlink()
|
||||
cron = self.env.ref('account_accountant.auto_reconcile_bank_statement_line')
|
||||
cron = self.env.ref('odex30_account_accountant.auto_reconcile_bank_statement_line')
|
||||
self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)]).unlink()
|
||||
|
||||
st_line = self._create_st_line(1234.0, partner_id=self.partner_a.id, date='2017-01-01')
|
||||
|
|
@ -2763,7 +2763,7 @@ class TestBankRecWidget(TestBankRecWidgetCommon):
|
|||
|
||||
def test_auto_reconcile_cron_with_time_limit(self):
|
||||
self.env['account.reconcile.model'].search([('company_id', '=', self.company_data['company'].id)]).unlink()
|
||||
cron = self.env.ref('account_accountant.auto_reconcile_bank_statement_line')
|
||||
cron = self.env.ref('odex30_account_accountant.auto_reconcile_bank_statement_line')
|
||||
self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)]).unlink()
|
||||
|
||||
st_line1 = self._create_st_line(1234.0, partner_id=self.partner_a.id, date='2017-01-01')
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ Keeps track of depreciations, and creates corresponding journal entries.
|
|||
'data/menuitems.xml',
|
||||
],
|
||||
'demo': [
|
||||
'demo/odex30_account_asset_demo.xml',
|
||||
'demo/account_asset_demo.xml',
|
||||
],
|
||||
'auto_install': True,
|
||||
'assets': {
|
||||
|
|
|
|||
Loading…
Reference in New Issue