Merge pull request #24370 from deepeshgarg007/loan_report_and_enhancements
feat: Loan report and enhancements
This commit is contained in:
commit
c3d82a889a
@ -23,7 +23,7 @@
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Reports",
|
||||
"links": "[\n {\n \"doctype\": \"Loan Repayment\",\n \"is_query_report\": true,\n \"label\": \"Loan Repayment and Closure\",\n \"name\": \"Loan Repayment and Closure\",\n \"route\": \"#query-report/Loan Repayment and Closure\",\n \"type\": \"report\"\n },\n {\n \"doctype\": \"Loan Security Pledge\",\n \"is_query_report\": true,\n \"label\": \"Loan Security Status\",\n \"name\": \"Loan Security Status\",\n \"route\": \"#query-report/Loan Security Status\",\n \"type\": \"report\"\n }\n]"
|
||||
"links": "[\n {\n \"doctype\": \"Loan Repayment\",\n \"is_query_report\": true,\n \"label\": \"Loan Repayment and Closure\",\n \"name\": \"Loan Repayment and Closure\",\n \"route\": \"#query-report/Loan Repayment and Closure\",\n \"type\": \"report\"\n },\n {\n \"doctype\": \"Loan Security Pledge\",\n \"is_query_report\": true,\n \"label\": \"Loan Security Status\",\n \"name\": \"Loan Security Status\",\n \"route\": \"#query-report/Loan Security Status\",\n \"type\": \"report\"\n },\n {\n \"doctype\": \"Loan Interest Accrual\",\n \"is_query_report\": true,\n \"label\": \"Loan Interest Report\",\n \"name\": \"Loan Interest Report\",\n \"route\": \"#query-report/Loan Interest Report\",\n \"type\": \"report\"\n },\n {\n \"doctype\": \"Loan Security\",\n \"is_query_report\": true,\n \"label\": \"Loan Security Exposure\",\n \"name\": \"Loan Security Exposure\",\n \"route\": \"#query-report/Loan Security Exposure\",\n \"type\": \"report\"\n },\n {\n \"doctype\": \"Loan Security\",\n \"is_query_report\": true,\n \"label\": \"Applicant-Wise Loan Security Exposure\",\n \"name\": \"Applicant-Wise Loan Security Exposure\",\n \"route\": \"#query-report/Applicant-Wise Loan Security Exposure\",\n \"type\": \"report\"\n }\n]"
|
||||
}
|
||||
],
|
||||
"category": "Modules",
|
||||
@ -38,7 +38,7 @@
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Loan",
|
||||
"modified": "2020-10-17 12:59:50.336085",
|
||||
"modified": "2021-01-17 07:21:22.092184",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan",
|
||||
|
@ -202,7 +202,9 @@ def request_loan_closure(loan, posting_date=None):
|
||||
|
||||
# checking greater than 0 as there may be some minor precision error
|
||||
if pending_amount < write_off_limit:
|
||||
# update status as loan closure requested
|
||||
# Auto create loan write off and update status as loan closure requested
|
||||
write_off = make_loan_write_off(loan)
|
||||
write_off.submit()
|
||||
frappe.db.set_value('Loan', loan, 'status', 'Loan Closure Requested')
|
||||
else:
|
||||
frappe.throw(_("Cannot close loan as there is an outstanding of {0}").format(pending_amount))
|
||||
@ -336,13 +338,13 @@ def create_loan_security_unpledge(unpledge_map, loan, company, applicant_type, a
|
||||
return unpledge_request
|
||||
|
||||
def validate_employee_currency_with_company_currency(applicant, company):
|
||||
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_employee_currency
|
||||
if not applicant:
|
||||
frappe.throw(_("Please select Applicant"))
|
||||
if not company:
|
||||
frappe.throw(_("Please select Company"))
|
||||
employee_currency = get_employee_currency(applicant)
|
||||
company_currency = erpnext.get_company_currency(company)
|
||||
if employee_currency != company_currency:
|
||||
frappe.throw(_("Loan cannot be repayed from salary for Employee {0} because salary is processed in currency {1}")
|
||||
.format(applicant, employee_currency))
|
||||
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_employee_currency
|
||||
if not applicant:
|
||||
frappe.throw(_("Please select Applicant"))
|
||||
if not company:
|
||||
frappe.throw(_("Please select Company"))
|
||||
employee_currency = get_employee_currency(applicant)
|
||||
company_currency = erpnext.get_company_currency(company)
|
||||
if employee_currency != company_currency:
|
||||
frappe.throw(_("Loan cannot be repayed from salary for Employee {0} because salary is processed in currency {1}")
|
||||
.format(applicant, employee_currency))
|
||||
|
@ -321,7 +321,7 @@ class TestLoan(unittest.TestCase):
|
||||
self.assertEquals(sum(pledged_qty.values()), 0)
|
||||
|
||||
amounts = amounts = calculate_amounts(loan.name, add_days(last_date, 5))
|
||||
self.assertTrue(amounts['pending_principal_amount'] < 0)
|
||||
self.assertEqual(amounts['pending_principal_amount'], 0)
|
||||
self.assertEquals(amounts['payable_principal_amount'], 0.0)
|
||||
self.assertEqual(amounts['interest_amount'], 0)
|
||||
|
||||
@ -473,7 +473,7 @@ class TestLoan(unittest.TestCase):
|
||||
self.assertEquals(loan.status, "Loan Closure Requested")
|
||||
|
||||
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
|
||||
self.assertTrue(amounts['pending_principal_amount'] < 0.0)
|
||||
self.assertEqual(amounts['pending_principal_amount'], 0.0)
|
||||
|
||||
def test_partial_unaccrued_interest_payment(self):
|
||||
pledge = [{
|
||||
|
@ -22,6 +22,7 @@
|
||||
"paid_principal_amount",
|
||||
"column_break_14",
|
||||
"interest_amount",
|
||||
"total_pending_interest_amount",
|
||||
"paid_interest_amount",
|
||||
"penalty_amount",
|
||||
"section_break_15",
|
||||
@ -172,13 +173,19 @@
|
||||
"hidden": 1,
|
||||
"label": "Last Accrual Date",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "total_pending_interest_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Pending Interest Amount",
|
||||
"options": "Company:company:default_currency"
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-07 05:49:25.448875",
|
||||
"modified": "2021-01-10 00:15:21.544140",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Interest Accrual",
|
||||
|
@ -100,6 +100,8 @@ def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_i
|
||||
interest_per_day = get_per_day_interest(pending_principal_amount, loan.rate_of_interest, posting_date)
|
||||
payable_interest = interest_per_day * no_of_days
|
||||
|
||||
pending_amounts = calculate_amounts(loan.name, posting_date, payment_type='Loan Closure')
|
||||
|
||||
args = frappe._dict({
|
||||
'loan': loan.name,
|
||||
'applicant_type': loan.applicant_type,
|
||||
@ -108,7 +110,8 @@ def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_i
|
||||
'loan_account': loan.loan_account,
|
||||
'pending_principal_amount': pending_principal_amount,
|
||||
'interest_amount': payable_interest,
|
||||
'penalty_amount': calculate_amounts(loan.name, posting_date)['penalty_amount'],
|
||||
'total_pending_interest_amount': pending_amounts['interest_amount'],
|
||||
'penalty_amount': pending_amounts['penalty_amount'],
|
||||
'process_loan_interest': process_loan_interest,
|
||||
'posting_date': posting_date,
|
||||
'accrual_type': accrual_type
|
||||
@ -202,6 +205,7 @@ def make_loan_interest_accrual_entry(args):
|
||||
loan_interest_accrual.loan_account = args.loan_account
|
||||
loan_interest_accrual.pending_principal_amount = flt(args.pending_principal_amount, precision)
|
||||
loan_interest_accrual.interest_amount = flt(args.interest_amount, precision)
|
||||
loan_interest_accrual.total_pending_interest_amount = flt(args.total_pending_interest_amount, precision)
|
||||
loan_interest_accrual.penalty_amount = flt(args.penalty_amount, precision)
|
||||
loan_interest_accrual.posting_date = args.posting_date or nowdate()
|
||||
loan_interest_accrual.process_loan_interest_accrual = args.process_loan_interest
|
||||
|
@ -37,10 +37,8 @@ class TestLoanInterestAccrual(unittest.TestCase):
|
||||
|
||||
loan_application = create_loan_application('_Test Company', self.applicant, 'Demand Loan', pledge)
|
||||
create_pledge(loan_application)
|
||||
|
||||
loan = create_demand_loan(self.applicant, "Demand Loan", loan_application,
|
||||
posting_date=get_first_day(nowdate()))
|
||||
|
||||
loan.submit()
|
||||
|
||||
first_date = '2019-10-01'
|
||||
@ -50,11 +48,46 @@ class TestLoanInterestAccrual(unittest.TestCase):
|
||||
|
||||
accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
|
||||
/ (days_in_year(get_datetime(first_date).year) * 100)
|
||||
|
||||
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
|
||||
|
||||
process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
|
||||
|
||||
loan_interest_accural = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name})
|
||||
|
||||
self.assertEquals(flt(loan_interest_accural.interest_amount, 0), flt(accrued_interest_amount, 0))
|
||||
|
||||
def test_accumulated_amounts(self):
|
||||
pledge = [{
|
||||
"loan_security": "Test Security 1",
|
||||
"qty": 4000.00
|
||||
}]
|
||||
|
||||
loan_application = create_loan_application('_Test Company', self.applicant, 'Demand Loan', pledge)
|
||||
create_pledge(loan_application)
|
||||
loan = create_demand_loan(self.applicant, "Demand Loan", loan_application,
|
||||
posting_date=get_first_day(nowdate()))
|
||||
loan.submit()
|
||||
|
||||
first_date = '2019-10-01'
|
||||
last_date = '2019-10-30'
|
||||
|
||||
no_of_days = date_diff(last_date, first_date) + 1
|
||||
accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
|
||||
/ (days_in_year(get_datetime(first_date).year) * 100)
|
||||
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
|
||||
process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
|
||||
loan_interest_accrual = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name})
|
||||
|
||||
self.assertEquals(flt(loan_interest_accrual.interest_amount, 0), flt(accrued_interest_amount, 0))
|
||||
|
||||
next_start_date = '2019-10-31'
|
||||
next_end_date = '2019-11-29'
|
||||
|
||||
no_of_days = date_diff(next_end_date, next_start_date) + 1
|
||||
process = process_loan_interest_accrual_for_demand_loans(posting_date=next_end_date)
|
||||
new_accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
|
||||
/ (days_in_year(get_datetime(first_date).year) * 100)
|
||||
|
||||
total_pending_interest_amount = flt(accrued_interest_amount + new_accrued_interest_amount, 0)
|
||||
|
||||
loan_interest_accrual = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name,
|
||||
'process_loan_interest_accrual': process})
|
||||
self.assertEquals(flt(loan_interest_accrual.total_pending_interest_amount, 0), total_pending_interest_amount)
|
||||
|
@ -377,7 +377,7 @@ def get_amounts(amounts, against_loan, posting_date):
|
||||
amounts["penalty_amount"] = flt(penalty_amount, precision)
|
||||
amounts["payable_amount"] = flt(payable_principal_amount + total_pending_interest + penalty_amount, precision)
|
||||
amounts["pending_accrual_entries"] = pending_accrual_entries
|
||||
amounts["unaccrued_interest"] = unaccrued_interest
|
||||
amounts["unaccrued_interest"] = flt(unaccrued_interest, precision)
|
||||
|
||||
if final_due_date:
|
||||
amounts["due_date"] = final_due_date
|
||||
|
@ -7,6 +7,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"loan_security",
|
||||
"loan_security_name",
|
||||
"loan_security_type",
|
||||
"column_break_2",
|
||||
"uom",
|
||||
@ -79,10 +80,18 @@
|
||||
"label": "Loan Security Type",
|
||||
"options": "Loan Security Type",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "loan_security.loan_security_name",
|
||||
"fieldname": "loan_security_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Loan Security Name",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-11 03:41:33.900340",
|
||||
"modified": "2021-01-17 07:41:49.598086",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Security Price",
|
||||
|
@ -144,17 +144,17 @@
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"description": "Pending amount that will be automatically ignored on loan closure request ",
|
||||
"description": "Loan Write Off will be automatically created on loan closure request if pending amount is below this limit",
|
||||
"fieldname": "write_off_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Write Off Amount ",
|
||||
"label": "Auto Write Off Amount ",
|
||||
"options": "Company:company:default_currency"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-26 07:13:55.029811",
|
||||
"modified": "2021-01-17 06:51:26.082879",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Type",
|
||||
|
@ -6,6 +6,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"loan_security",
|
||||
"loan_security_name",
|
||||
"loan_security_type",
|
||||
"loan_security_code",
|
||||
"uom",
|
||||
@ -85,11 +86,18 @@
|
||||
"label": "Post Haircut Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "loan_security.loan_security_name",
|
||||
"fieldname": "loan_security_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Loan Security Name",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-05 10:07:15.424937",
|
||||
"modified": "2021-01-17 07:41:12.452514",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Pledge",
|
||||
|
@ -30,7 +30,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-02-01 08:14:05.845161",
|
||||
"modified": "2021-01-17 03:59:14.494557",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Process Loan Security Shortfall",
|
||||
@ -45,7 +45,9 @@
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
@ -57,7 +59,9 @@
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Loan Manager",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
|
@ -6,6 +6,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"loan_security",
|
||||
"loan_security_name",
|
||||
"qty",
|
||||
"loan_security_price",
|
||||
"amount",
|
||||
@ -56,12 +57,19 @@
|
||||
"label": "Post Haircut Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "loan_security.loan_security_name",
|
||||
"fieldname": "loan_security_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Loan Security Name",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-05 10:07:37.542344",
|
||||
"modified": "2021-01-17 07:29:01.671722",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Proposed Pledge",
|
||||
|
@ -6,6 +6,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"loan_security",
|
||||
"loan_security_name",
|
||||
"loan_security_type",
|
||||
"loan_security_code",
|
||||
"haircut",
|
||||
@ -61,12 +62,19 @@
|
||||
"fieldtype": "Percent",
|
||||
"label": "Haircut",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "loan_security.loan_security_name",
|
||||
"fieldname": "loan_security_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Loan Security Name",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-05 10:07:28.106961",
|
||||
"modified": "2021-01-17 07:36:20.212342",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Unpledge",
|
||||
|
@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Applicant-Wise Loan Security Exposure"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
}
|
||||
]
|
||||
};
|
@ -0,0 +1,29 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2021-01-15 23:48:38.913514",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2021-01-15 23:48:38.913514",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Applicant-Wise Loan Security Exposure",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Loan Security",
|
||||
"report_name": "Applicant-Wise Loan Security Exposure",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "System Manager"
|
||||
},
|
||||
{
|
||||
"role": "Loan Manager"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import erpnext
|
||||
from frappe import _
|
||||
from frappe.utils import get_datetime, flt
|
||||
from six import iteritems
|
||||
|
||||
def execute(filters=None):
|
||||
columns = get_columns(filters)
|
||||
data = get_data(filters)
|
||||
return columns, data
|
||||
|
||||
|
||||
def get_columns(filters):
|
||||
columns = [
|
||||
{"label": _("Applicant Type"), "fieldname": "applicant_type", "options": "DocType", "width": 100},
|
||||
{"label": _("Applicant Name"), "fieldname": "applicant_name", "fieldtype": "Dynamic Link", "options": "applicant_type", "width": 150},
|
||||
{"label": _("Loan Security"), "fieldname": "loan_security", "fieldtype": "Link", "options": "Loan Security", "width": 160},
|
||||
{"label": _("Loan Security Code"), "fieldname": "loan_security_code", "fieldtype": "Data", "width": 100},
|
||||
{"label": _("Loan Security Name"), "fieldname": "loan_security_name", "fieldtype": "Data", "width": 150},
|
||||
{"label": _("Haircut"), "fieldname": "haircut", "fieldtype": "Percent", "width": 100},
|
||||
{"label": _("Loan Security Type"), "fieldname": "loan_security_type", "fieldtype": "Link", "options": "Loan Security Type", "width": 120},
|
||||
{"label": _("Disabled"), "fieldname": "disabled", "fieldtype": "Check", "width": 80},
|
||||
{"label": _("Total Qty"), "fieldname": "total_qty", "fieldtype": "Float", "width": 100},
|
||||
{"label": _("Latest Price"), "fieldname": "latest_price", "fieldtype": "Currency", "options": "currency", "width": 100},
|
||||
{"label": _("Current Value"), "fieldname": "current_value", "fieldtype": "Currency", "options": "currency", "width": 100},
|
||||
{"label": _("% Of Applicant Portfolio"), "fieldname": "portfolio_percent", "fieldtype": "Percentage", "width": 100},
|
||||
{"label": _("Currency"), "fieldname": "currency", "fieldtype": "Currency", "options": "Currency", "hidden": 1, "width": 100},
|
||||
]
|
||||
|
||||
return columns
|
||||
|
||||
def get_data(filters):
|
||||
data = []
|
||||
loan_security_details = get_loan_security_details(filters)
|
||||
pledge_values, total_value_map, applicant_type_map = get_applicant_wise_total_loan_security_qty(filters,
|
||||
loan_security_details)
|
||||
|
||||
currency = erpnext.get_company_currency(filters.get('company'))
|
||||
|
||||
for key, qty in iteritems(pledge_values):
|
||||
row = {}
|
||||
current_value = flt(qty * loan_security_details.get(key[1])['latest_price'])
|
||||
row.update(loan_security_details.get(key[1]))
|
||||
row.update({
|
||||
'applicant_type': applicant_type_map.get(key[0]),
|
||||
'applicant_name': key[0],
|
||||
'total_qty': qty,
|
||||
'current_value': current_value,
|
||||
'portfolio_percent': flt(current_value * 100 / total_value_map.get(key[0]), 2),
|
||||
'currency': currency
|
||||
})
|
||||
|
||||
data.append(row)
|
||||
|
||||
return data
|
||||
|
||||
def get_loan_security_details(filters):
|
||||
security_detail_map = {}
|
||||
|
||||
loan_security_price_map = frappe._dict(frappe.db.sql("""
|
||||
SELECT loan_security, loan_security_price
|
||||
FROM `tabLoan Security Price` t1
|
||||
WHERE valid_from >= (SELECT MAX(valid_from) FROM `tabLoan Security Price` t2
|
||||
WHERE t1.loan_security = t2.loan_security)
|
||||
""", as_list=1))
|
||||
|
||||
loan_security_details = frappe.get_all('Loan Security', fields=['name as loan_security',
|
||||
'loan_security_code', 'loan_security_name', 'haircut', 'loan_security_type',
|
||||
'disabled'])
|
||||
|
||||
for security in loan_security_details:
|
||||
security.update({'latest_price': flt(loan_security_price_map.get(security.loan_security))})
|
||||
security_detail_map.setdefault(security.loan_security, security)
|
||||
|
||||
return security_detail_map
|
||||
|
||||
def get_applicant_wise_total_loan_security_qty(filters, loan_security_details):
|
||||
current_pledges = {}
|
||||
total_value_map = {}
|
||||
applicant_type_map = {}
|
||||
applicant_wise_unpledges = {}
|
||||
conditions = ""
|
||||
|
||||
if filters.get('company'):
|
||||
conditions = "AND company = %(company)s"
|
||||
|
||||
unpledges = frappe.db.sql("""
|
||||
SELECT up.applicant, u.loan_security, sum(u.qty) as qty
|
||||
FROM `tabLoan Security Unpledge` up, `tabUnpledge` u
|
||||
WHERE u.parent = up.name
|
||||
AND up.status = 'Approved'
|
||||
{conditions}
|
||||
GROUP BY up.applicant, u.loan_security
|
||||
""".format(conditions=conditions), filters, as_dict=1)
|
||||
|
||||
for unpledge in unpledges:
|
||||
applicant_wise_unpledges.setdefault((unpledge.applicant, unpledge.loan_security), unpledge.qty)
|
||||
|
||||
pledges = frappe.db.sql("""
|
||||
SELECT lp.applicant_type, lp.applicant, p.loan_security, sum(p.qty) as qty
|
||||
FROM `tabLoan Security Pledge` lp, `tabPledge`p
|
||||
WHERE p.parent = lp.name
|
||||
AND lp.status = 'Pledged'
|
||||
{conditions}
|
||||
GROUP BY lp.applicant, p.loan_security
|
||||
""".format(conditions=conditions), filters, as_dict=1)
|
||||
|
||||
for security in pledges:
|
||||
current_pledges.setdefault((security.applicant, security.loan_security), security.qty)
|
||||
total_value_map.setdefault(security.applicant, 0.0)
|
||||
applicant_type_map.setdefault(security.applicant, security.applicant_type)
|
||||
|
||||
current_pledges[(security.applicant, security.loan_security)] -= \
|
||||
applicant_wise_unpledges.get((security.applicant, security.loan_security), 0.0)
|
||||
|
||||
total_value_map[security.applicant] += current_pledges.get((security.applicant, security.loan_security)) \
|
||||
* loan_security_details.get(security.loan_security)['latest_price']
|
||||
|
||||
return current_pledges, total_value_map, applicant_type_map
|
@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Loan Interest Report"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
}
|
||||
]
|
||||
};
|
@ -0,0 +1,29 @@
|
||||
{
|
||||
"add_total_row": 1,
|
||||
"columns": [],
|
||||
"creation": "2021-01-10 02:03:26.742693",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2021-01-10 02:03:26.742693",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Interest Report",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Loan Interest Accrual",
|
||||
"report_name": "Loan Interest Report",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "System Manager"
|
||||
},
|
||||
{
|
||||
"role": "Loan Manager"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import erpnext
|
||||
from frappe import _
|
||||
from frappe.utils import flt, getdate, add_days
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
columns = get_columns(filters)
|
||||
data = get_active_loan_details(filters)
|
||||
return columns, data
|
||||
|
||||
def get_columns(filters):
|
||||
columns = [
|
||||
{"label": _("Loan"), "fieldname": "loan", "fieldtype": "Link", "options": "Loan", "width": 160},
|
||||
{"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 160},
|
||||
{"label": _("Applicant Type"), "fieldname": "applicant_type", "options": "DocType", "width": 100},
|
||||
{"label": _("Applicant Name"), "fieldname": "applicant_name", "fieldtype": "Dynamic Link", "options": "applicant_type", "width": 150},
|
||||
{"label": _("Loan Type"), "fieldname": "loan_type", "fieldtype": "Link", "options": "Loan Type", "width": 100},
|
||||
{"label": _("Sanctioned Amount"), "fieldname": "sanctioned_amount", "fieldtype": "Currency", "options": "currency", "width": 120},
|
||||
{"label": _("Disbursed Amount"), "fieldname": "disbursed_amount", "fieldtype": "Currency", "options": "currency", "width": 120},
|
||||
{"label": _("Penalty Amount"), "fieldname": "penalty", "fieldtype": "Currency", "options": "currency", "width": 120},
|
||||
{"label": _("Accrued Interest"), "fieldname": "accrued_interest", "fieldtype": "Currency", "options": "currency", "width": 120},
|
||||
{"label": _("Total Repayment"), "fieldname": "total_repayment", "fieldtype": "Currency", "options": "currency", "width": 120},
|
||||
{"label": _("Principal Outstanding"), "fieldname": "principal_outstanding", "fieldtype": "Currency", "options": "currency", "width": 120},
|
||||
{"label": _("Interest Outstanding"), "fieldname": "interest_outstanding", "fieldtype": "Currency", "options": "currency", "width": 120},
|
||||
{"label": _("Total Outstanding"), "fieldname": "total_outstanding", "fieldtype": "Currency", "options": "currency", "width": 120},
|
||||
{"label": _("Undue Booked Interest"), "fieldname": "undue_interest", "fieldtype": "Currency", "options": "currency", "width": 120},
|
||||
{"label": _("Interest %"), "fieldname": "rate_of_interest", "fieldtype": "Percent", "width": 100},
|
||||
{"label": _("Penalty Interest %"), "fieldname": "penalty_interest", "fieldtype": "Percent", "width": 100},
|
||||
{"label": _("Currency"), "fieldname": "currency", "fieldtype": "Currency", "options": "Currency", "hidden": 1, "width": 100},
|
||||
]
|
||||
|
||||
return columns
|
||||
|
||||
def get_active_loan_details(filters):
|
||||
|
||||
filter_obj = {"status": ("!=", "Closed")}
|
||||
if filters.get('company'):
|
||||
filter_obj.update({'company': filters.get('company')})
|
||||
|
||||
loan_details = frappe.get_all("Loan",
|
||||
fields=["name as loan", "applicant_type", "applicant as applicant_name", "loan_type",
|
||||
"disbursed_amount", "rate_of_interest", "total_payment", "total_principal_paid",
|
||||
"total_interest_payable", "written_off_amount", "status"],
|
||||
filters=filter_obj)
|
||||
|
||||
loan_list = [d.loan for d in loan_details]
|
||||
|
||||
sanctioned_amount_map = get_sanctioned_amount_map()
|
||||
penal_interest_rate_map = get_penal_interest_rate_map()
|
||||
payments = get_payments(loan_list)
|
||||
accrual_map = get_interest_accruals(loan_list)
|
||||
currency = erpnext.get_company_currency(filters.get('company'))
|
||||
|
||||
for loan in loan_details:
|
||||
loan.update({
|
||||
"sanctioned_amount": flt(sanctioned_amount_map.get(loan.applicant_name)),
|
||||
"principal_outstanding": flt(loan.total_payment) - flt(loan.total_principal_paid) \
|
||||
- flt(loan.total_interest_payable) - flt(loan.written_off_amount),
|
||||
"total_repayment": flt(payments.get(loan.loan)),
|
||||
"accrued_interest": flt(accrual_map.get(loan.loan, {}).get("accrued_interest")),
|
||||
"interest_outstanding": flt(accrual_map.get(loan.loan, {}).get("interest_outstanding")),
|
||||
"penalty": flt(accrual_map.get(loan.loan, {}).get("penalty")),
|
||||
"penalty_interest": penal_interest_rate_map.get(loan.loan_type),
|
||||
"undue_interest": flt(accrual_map.get(loan.loan, {}).get("undue_interest")),
|
||||
"currency": currency
|
||||
})
|
||||
|
||||
loan['total_outstanding'] = loan['principal_outstanding'] + loan['interest_outstanding'] \
|
||||
+ loan['penalty']
|
||||
|
||||
return loan_details
|
||||
|
||||
def get_sanctioned_amount_map():
|
||||
return frappe._dict(frappe.get_all("Sanctioned Loan Amount", fields=["applicant", "sanctioned_amount_limit"],
|
||||
as_list=1))
|
||||
|
||||
def get_payments(loans):
|
||||
return frappe._dict(frappe.get_all("Loan Repayment", fields=["against_loan", "sum(amount_paid)"],
|
||||
filters={"against_loan": ("in", loans)}, group_by="against_loan", as_list=1))
|
||||
|
||||
def get_interest_accruals(loans):
|
||||
accrual_map = {}
|
||||
|
||||
interest_accruals = frappe.get_all("Loan Interest Accrual",
|
||||
fields=["loan", "interest_amount", "posting_date", "penalty_amount",
|
||||
"paid_interest_amount", "accrual_type"], filters={"loan": ("in", loans)}, order_by="posting_date desc")
|
||||
|
||||
for entry in interest_accruals:
|
||||
accrual_map.setdefault(entry.loan, {
|
||||
"accrued_interest": 0.0,
|
||||
"undue_interest": 0.0,
|
||||
"interest_outstanding": 0.0,
|
||||
"last_accrual_date": '',
|
||||
"due_date": ''
|
||||
})
|
||||
|
||||
if entry.accrual_type == 'Regular':
|
||||
if not accrual_map[entry.loan]['due_date']:
|
||||
accrual_map[entry.loan]['due_date'] = add_days(entry.posting_date, 1)
|
||||
if not accrual_map[entry.loan]['last_accrual_date']:
|
||||
accrual_map[entry.loan]['last_accrual_date'] = entry.posting_date
|
||||
|
||||
due_date = accrual_map[entry.loan]['due_date']
|
||||
last_accrual_date = accrual_map[entry.loan]['last_accrual_date']
|
||||
|
||||
if due_date and getdate(entry.posting_date) < getdate(due_date):
|
||||
accrual_map[entry.loan]["interest_outstanding"] += entry.interest_amount - entry.paid_interest_amount
|
||||
else:
|
||||
accrual_map[entry.loan]['undue_interest'] += entry.interest_amount - entry.paid_interest_amount
|
||||
|
||||
accrual_map[entry.loan]["accrued_interest"] += entry.interest_amount
|
||||
|
||||
if last_accrual_date and getdate(entry.posting_date) == last_accrual_date:
|
||||
accrual_map[entry.loan]["penalty"] = entry.penalty_amount
|
||||
|
||||
return accrual_map
|
||||
|
||||
def get_penal_interest_rate_map():
|
||||
return frappe._dict(frappe.get_all("Loan Type", fields=["name", "penalty_interest_rate"], as_list=1))
|
@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Loan Security Exposure"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
}
|
||||
]
|
||||
};
|
@ -0,0 +1,29 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2021-01-16 08:08:01.694583",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2021-01-16 08:08:01.694583",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Security Exposure",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Loan Security",
|
||||
"report_name": "Loan Security Exposure",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "System Manager"
|
||||
},
|
||||
{
|
||||
"role": "Loan Manager"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import erpnext
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
from six import iteritems
|
||||
from erpnext.loan_management.report.applicant_wise_loan_security_exposure.applicant_wise_loan_security_exposure \
|
||||
import get_loan_security_details, get_applicant_wise_total_loan_security_qty
|
||||
|
||||
def execute(filters=None):
|
||||
columns = get_columns(filters)
|
||||
data = get_data(filters)
|
||||
return columns, data
|
||||
|
||||
def get_columns(filters):
|
||||
columns = [
|
||||
{"label": _("Loan Security"), "fieldname": "loan_security", "fieldtype": "Link", "options": "Loan Security", "width": 160},
|
||||
{"label": _("Loan Security Code"), "fieldname": "loan_security_code", "fieldtype": "Data", "width": 100},
|
||||
{"label": _("Loan Security Name"), "fieldname": "loan_security_name", "fieldtype": "Data", "width": 150},
|
||||
{"label": _("Haircut"), "fieldname": "haircut", "fieldtype": "Percent", "width": 100},
|
||||
{"label": _("Loan Security Type"), "fieldname": "loan_security_type", "fieldtype": "Link", "options": "Loan Security Type", "width": 120},
|
||||
{"label": _("Disabled"), "fieldname": "disabled", "fieldtype": "Check", "width": 80},
|
||||
{"label": _("Total Qty"), "fieldname": "total_qty", "fieldtype": "Float", "width": 100},
|
||||
{"label": _("Latest Price"), "fieldname": "latest_price", "fieldtype": "Currency", "options": "currency", "width": 100},
|
||||
{"label": _("Current Value"), "fieldname": "current_value", "fieldtype": "Currency", "options": "currency", "width": 100},
|
||||
{"label": _("% Of Total Portfolio"), "fieldname": "portfolio_percent", "fieldtype": "Percentage", "width": 100},
|
||||
{"label": _("Pledged Applicant Count"), "fieldname": "pledged_applicant_count", "fieldtype": "Percentage", "width": 100},
|
||||
{"label": _("Currency"), "fieldname": "currency", "fieldtype": "Currency", "options": "Currency", "hidden": 1, "width": 100},
|
||||
]
|
||||
|
||||
return columns
|
||||
|
||||
def get_data(filters):
|
||||
data = []
|
||||
loan_security_details = get_loan_security_details(filters)
|
||||
current_pledges, total_portfolio_value = get_company_wise_loan_security_details(filters, loan_security_details)
|
||||
currency = erpnext.get_company_currency(filters.get('company'))
|
||||
|
||||
for security, value in iteritems(current_pledges):
|
||||
row = {}
|
||||
current_value = flt(value['qty'] * loan_security_details.get(security)['latest_price'])
|
||||
row.update(loan_security_details.get(security))
|
||||
row.update({
|
||||
'total_qty': value['qty'],
|
||||
'current_value': current_value,
|
||||
'portfolio_percent': flt(current_value * 100 / total_portfolio_value, 2),
|
||||
'pledged_applicant_count': value['applicant_count'],
|
||||
'currency': currency
|
||||
})
|
||||
|
||||
data.append(row)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_company_wise_loan_security_details(filters, loan_security_details):
|
||||
pledge_values, total_value_map, applicant_type_map = get_applicant_wise_total_loan_security_qty(filters,
|
||||
loan_security_details)
|
||||
|
||||
total_portfolio_value = 0
|
||||
security_wise_map = {}
|
||||
for key, qty in iteritems(pledge_values):
|
||||
security_wise_map.setdefault(key[1], {
|
||||
'qty': 0.0,
|
||||
'applicant_count': 0.0
|
||||
})
|
||||
|
||||
security_wise_map[key[1]]['qty'] += qty
|
||||
if qty:
|
||||
security_wise_map[key[1]]['applicant_count'] += 1
|
||||
|
||||
total_portfolio_value += flt(qty * loan_security_details.get(key[1])['latest_price'])
|
||||
|
||||
return security_wise_map, total_portfolio_value
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user