diff --git a/.github/helper/install.sh b/.github/helper/install.sh
index 125c8d94..4b8651f1 100644
--- a/.github/helper/install.sh
+++ b/.github/helper/install.sh
@@ -4,8 +4,9 @@ set -e
cd ~ || exit
-sudo apt-get update
-sudo apt-get -y install redis-server libcups2-dev -qq
+sudo apt update
+sudo apt remove mysql-server mysql-client
+sudo apt install libcups2-dev redis-server mariadb-client-10.6
pip install frappe-bench
@@ -15,15 +16,14 @@ bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frapp
mkdir ~/frappe-bench/sites/test_site
cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config.json" ~/frappe-bench/sites/test_site/
-mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"
-mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
+mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
+mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
-mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
-mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE DATABASE test_frappe"
-mysql --host 127.0.0.1 --port 3306 -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
+mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
+mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE DATABASE test_frappe"
+mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
-mysql --host 127.0.0.1 --port 3306 -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'"
-mysql --host 127.0.0.1 --port 3306 -u root -e "FLUSH PRIVILEGES"
+mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "FLUSH PRIVILEGES"
install_whktml() {
wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
diff --git a/.github/helper/site_config.json b/.github/helper/site_config.json
index 28847e40..21fdbf31 100644
--- a/.github/helper/site_config.json
+++ b/.github/helper/site_config.json
@@ -9,7 +9,7 @@
"mail_password": "test",
"admin_password": "admin",
"root_login": "root",
- "root_password": "travis",
+ "root_password": "root",
"host_name": "http://test_site:8000",
"install_apps": ["payments", "erpnext"],
"throttle_user_limit": 100
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 45421ff4..6c564a87 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -36,16 +36,16 @@ jobs:
strategy:
fail-fast: false
- name: Server
+ name: Python Unit Tests
services:
mysql:
- image: mariadb:10.3
+ image: mariadb:10.6
env:
- MYSQL_ALLOW_EMPTY_PASSWORD: YES
+ MARIADB_ROOT_PASSWORD: 'root'
ports:
- 3306:3306
- options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
+ options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3
steps:
- name: Clone
diff --git a/lending/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py b/lending/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py
index 85fa48df..0fe80aec 100644
--- a/lending/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py
+++ b/lending/loan_management/dashboard_chart_source/top_10_pledged_loan_securities/top_10_pledged_loan_securities.py
@@ -47,7 +47,7 @@ def get_data(
frappe.db.sql(
"""
SELECT u.loan_security, sum(u.qty) as qty
- FROM `tabLoan Security Unpledge` up, `tabUnpledge` u
+ FROM `tabLoan Collateral Deassignment` up, `tabUnpledge` u
WHERE u.parent = up.name
AND up.status = 'Approved'
{conditions}
@@ -64,9 +64,9 @@ def get_data(
frappe.db.sql(
"""
SELECT p.loan_security, sum(p.qty) as qty
- FROM `tabLoan Security Pledge` lp, `tabPledge`p
+ FROM `tabLoan Collateral Assignment` lp, `tabPledge`p
WHERE p.parent = lp.name
- AND lp.status = 'Pledged'
+ AND lp.status = 'Assigned'
{conditions}
GROUP BY p.loan_security
""".format(
diff --git a/lending/loan_management/doctype/loan/loan.js b/lending/loan_management/doctype/loan/loan.js
index f9f72b5e..48157d00 100644
--- a/lending/loan_management/doctype/loan/loan.js
+++ b/lending/loan_management/doctype/loan/loan.js
@@ -7,13 +7,13 @@ frappe.ui.form.on('Loan', {
setup: function(frm) {
frm.make_methods = {
'Loan Disbursement': function() { frm.trigger('make_loan_disbursement') },
- 'Loan Security Unpledge': function() { frm.trigger('create_loan_security_unpledge') },
+ 'Loan Collateral Deassignment': function() { frm.trigger('create_loan_collateral_deassignment') },
'Loan Write Off': function() { frm.trigger('make_loan_write_off_entry') }
}
},
onload: function (frm) {
- // Ignore loan security pledge on cancel of loan
- frm.ignore_doctypes_on_cancel_all = ["Loan Security Pledge", "Loan Repayment Schedule"];
+ // Ignore Loan Collateral Assignment on cancel of loan
+ frm.ignore_doctypes_on_cancel_all = ["Loan Collateral Assignment", "Loan Repayment Schedule"];
frm.set_query("loan_application", function () {
return {
@@ -82,8 +82,8 @@ frappe.ui.form.on('Loan', {
}
if (frm.doc.status == "Loan Closure Requested") {
- frm.add_custom_button(__('Loan Security Unpledge'), function() {
- frm.trigger("create_loan_security_unpledge");
+ frm.add_custom_button(__('Loan Collateral Deassignment'), function() {
+ frm.trigger("create_loan_collateral_deassignment");
},__('Create'));
}
@@ -219,7 +219,7 @@ frappe.ui.form.on('Loan', {
);
},
- create_loan_security_unpledge: function(frm) {
+ create_loan_collateral_deassignment: function(frm) {
frappe.call({
method: "lending.loan_management.doctype.loan.loan.unpledge_security",
args : {
@@ -245,23 +245,34 @@ frappe.ui.form.on('Loan', {
if (!r.exc && r.message) {
let loan_fields = ["loan_product", "loan_amount", "repayment_method",
- "monthly_repayment_amount", "repayment_periods", "rate_of_interest", "is_secured_loan"]
+ "monthly_repayment_amount", "repayment_periods", "rate_of_interest", "is_secured_loan", "collateral_type"]
loan_fields.forEach(field => {
frm.set_value(field, r.message[field]);
});
if (frm.doc.is_secured_loan) {
- $.each(r.message.proposed_pledges, function(i, d) {
- let row = frm.add_child("securities");
- row.loan_security = d.loan_security;
- row.qty = d.qty;
- row.loan_security_price = d.loan_security_price;
- row.amount = d.amount;
- row.haircut = d.haircut;
- });
-
- frm.refresh_fields("securities");
+ if (frm.doc.collateral_type === "Loan Security") {
+ $.each(r.message.proposed_pledges, function(i, d) {
+ let row = frm.add_child("securities");
+ row.loan_security = d.loan_security;
+ row.qty = d.qty;
+ row.loan_security_price = d.loan_security_price;
+ row.amount = d.amount;
+ row.haircut = d.haircut;
+ });
+
+ frm.refresh_fields("securities");
+ } else if (frm.doc.collateral_type === "Loan Collateral") {
+ $.each(r.message.proposed_collaterals, function(i, d) {
+ let row = frm.add_child("collaterals");
+ row.loan_collateral = d.loan_collateral;
+ row.loan_collateral_name = d.loan_collateral_name;
+ row.available_collateral_value = d.available_collateral_value;
+ });
+
+ frm.refresh_fields("collaterals");
+ }
}
}
}
diff --git a/lending/loan_management/doctype/loan/loan.json b/lending/loan_management/doctype/loan/loan.json
index c1600f3c..be7b8247 100644
--- a/lending/loan_management/doctype/loan/loan.json
+++ b/lending/loan_management/doctype/loan/loan.json
@@ -23,6 +23,7 @@
"loan_amount",
"rate_of_interest",
"is_secured_loan",
+ "collateral_type",
"disbursement_date",
"closure_date",
"disbursed_amount",
@@ -474,12 +475,20 @@
"fieldname": "loan_classification_details_section",
"fieldtype": "Section Break",
"label": "Loan Classification Details"
+ },
+ {
+ "depends_on": "eval:doc.is_secured_loan",
+ "fieldname": "collateral_type",
+ "fieldtype": "Select",
+ "label": "Collateral Type",
+ "mandatory_depends_on": "eval:doc.is_secured_loan",
+ "options": "\nLoan Security\nLoan Collateral"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-10-11 13:26:31.406754",
+ "modified": "2023-10-19 05:37:08.804432",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan",
diff --git a/lending/loan_management/doctype/loan/loan.py b/lending/loan_management/doctype/loan/loan.py
index 2411c43c..38cfa03f 100644
--- a/lending/loan_management/doctype/loan/loan.py
+++ b/lending/loan_management/doctype/loan/loan.py
@@ -22,7 +22,10 @@
from erpnext.accounts.doctype.journal_entry.journal_entry import get_payment_entry
from erpnext.controllers.accounts_controller import AccountsController
-from lending.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import (
+from lending.loan_management.doctype.loan_collateral.loan_collateral import (
+ get_pending_deassignment_collaterals,
+)
+from lending.loan_management.doctype.loan_collateral_deassignment.loan_collateral_deassignment import (
get_pledged_security_qty,
)
@@ -90,13 +93,13 @@ def set_cyclic_date(self):
self.repayment_start_date = cyclic_date
def on_submit(self):
- self.link_loan_security_pledge()
+ self.link_loan_collateral_assignment()
# Interest accrual for backdated term loans
self.accrue_loan_interest()
self.submit_draft_schedule()
def on_cancel(self):
- self.unlink_loan_security_pledge()
+ self.unlink_loan_collateral_assignment()
self.cancel_and_delete_repayment_schedule()
self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"]
@@ -221,10 +224,10 @@ def validate_loan_amount(self):
if not self.loan_amount:
frappe.throw(_("Loan amount is mandatory"))
- def link_loan_security_pledge(self):
+ def link_loan_collateral_assignment(self):
if self.is_secured_loan and self.loan_application:
maximum_loan_value = frappe.db.get_value(
- "Loan Security Pledge",
+ "Loan Collateral Assignment",
{"loan_application": self.loan_application, "status": "Requested"},
"sum(maximum_loan_value)",
)
@@ -232,8 +235,8 @@ def link_loan_security_pledge(self):
if maximum_loan_value:
frappe.db.sql(
"""
- UPDATE `tabLoan Security Pledge`
- SET loan = %s, pledge_time = %s, status = 'Pledged'
+ UPDATE `tabLoan Collateral Assignment`
+ SET loan = %s, pledge_time = %s, status = 'Assigned'
WHERE status = 'Requested' and loan_application = %s
""",
(self.name, now_datetime(), self.loan_application),
@@ -251,13 +254,15 @@ def accrue_loan_interest(self):
posting_date=getdate(), loan_product=self.loan_product, loan=self.name
)
- def unlink_loan_security_pledge(self):
- pledges = frappe.get_all("Loan Security Pledge", fields=["name"], filters={"loan": self.name})
+ def unlink_loan_collateral_assignment(self):
+ pledges = frappe.get_all(
+ "Loan Collateral Assignment", fields=["name"], filters={"loan": self.name}
+ )
pledge_list = [d.name for d in pledges]
if pledge_list:
frappe.db.sql(
- """UPDATE `tabLoan Security Pledge` SET
- loan = '', status = 'Unpledged'
+ """UPDATE `tabLoan Collateral Assignment` SET
+ loan = '', status = 'Unassigned'
where name in (%s) """
% (", ".join(["%s"] * len(pledge_list))),
tuple(pledge_list),
@@ -458,31 +463,55 @@ def make_loan_write_off(loan, company=None, posting_date=None, amount=0, as_dict
@frappe.whitelist()
def unpledge_security(
- loan=None, loan_security_pledge=None, security_map=None, as_dict=0, save=0, submit=0, approve=0
+ loan=None,
+ loan_collateral_assignment=None,
+ security_map=None,
+ collaterals=None,
+ collateral_type=None,
+ as_dict=0,
+ save=0,
+ submit=0,
+ approve=0,
):
# if no security_map is passed it will be considered as full unpledge
if security_map and isinstance(security_map, str):
security_map = json.loads(security_map)
if loan:
- pledge_qty_map = security_map or get_pledged_security_qty(loan)
- loan_doc = frappe.get_doc("Loan", loan)
- unpledge_request = create_loan_security_unpledge(
- pledge_qty_map, loan_doc.name, loan_doc.company, loan_doc.applicant_type, loan_doc.applicant
+ loan_company, loan_applicant_type, loan_applicant, loan_collateral_type = frappe.db.get_value(
+ "Loan", loan, ["company", "applicant_type", "applicant", "collateral_type"]
+ )
+
+ unpledge_map = None
+ deassignment_collaterals = None
+
+ if loan_collateral_type == "Loan Security":
+ unpledge_map = security_map or get_pledged_security_qty(loan)
+ else:
+ deassignment_collaterals = collaterals or get_pending_deassignment_collaterals(loan)
+ unpledge_request = create_loan_collateral_deassignment(
+ loan,
+ loan_company,
+ loan_applicant_type,
+ loan_applicant,
+ loan_collateral_type,
+ unpledge_map,
+ deassignment_collaterals,
)
- # will unpledge qty based on loan security pledge
- elif loan_security_pledge:
- security_map = {}
- pledge_doc = frappe.get_doc("Loan Security Pledge", loan_security_pledge)
+ # will unpledge qty based on Loan Collateral Assignment
+ elif loan_collateral_assignment:
+ unpledge_map = {}
+ pledge_doc = frappe.get_doc("Loan Collateral Assignment", loan_collateral_assignment)
for security in pledge_doc.securities:
- security_map.setdefault(security.loan_security, security.qty)
+ unpledge_map.setdefault(security.loan_security, security.qty)
- unpledge_request = create_loan_security_unpledge(
- security_map,
+ unpledge_request = create_loan_collateral_deassignment(
pledge_doc.loan,
pledge_doc.company,
pledge_doc.applicant_type,
pledge_doc.applicant,
+ collateral_type,
+ unpledge_map,
)
if save:
@@ -504,16 +533,29 @@ def unpledge_security(
return unpledge_request
-def create_loan_security_unpledge(unpledge_map, loan, company, applicant_type, applicant):
- unpledge_request = frappe.new_doc("Loan Security Unpledge")
+def create_loan_collateral_deassignment(
+ loan,
+ company,
+ applicant_type,
+ applicant,
+ collateral_type,
+ unpledge_map=None,
+ deassignment_collaterals=None,
+):
+ unpledge_request = frappe.new_doc("Loan Collateral Deassignment")
unpledge_request.applicant_type = applicant_type
unpledge_request.applicant = applicant
unpledge_request.loan = loan
unpledge_request.company = company
+ unpledge_request.collateral_type = collateral_type
- for security, qty in unpledge_map.items():
- if qty:
- unpledge_request.append("securities", {"loan_security": security, "qty": qty})
+ if collateral_type == "Loan Security":
+ for security, qty in unpledge_map.items():
+ if qty:
+ unpledge_request.append("securities", {"loan_security": security, "qty": qty})
+ else:
+ for loan_collateral in deassignment_collaterals:
+ unpledge_request.append("collaterals", {"loan_collateral": loan_collateral})
return unpledge_request
diff --git a/lending/loan_management/doctype/loan/loan_dashboard.py b/lending/loan_management/doctype/loan/loan_dashboard.py
index 48eae0e3..31831fb6 100644
--- a/lending/loan_management/doctype/loan/loan_dashboard.py
+++ b/lending/loan_management/doctype/loan/loan_dashboard.py
@@ -10,12 +10,19 @@ def get_data():
{
"items": [
"Loan Repayment Schedule",
- "Loan Security Pledge",
+ "Loan Collateral Assignment",
"Loan Security Shortfall",
"Loan Disbursement",
]
},
{"items": ["Loan Repayment", "Loan Interest Accrual", "Loan Write Off", "Loan Restructure"]},
- {"items": ["Loan Security Unpledge", "Days Past Due Log", "Journal Entry", "Sales Invoice"]},
+ {
+ "items": [
+ "Loan Collateral Deassignment",
+ "Days Past Due Log",
+ "Journal Entry",
+ "Sales Invoice",
+ ]
+ },
],
}
diff --git a/lending/loan_management/doctype/loan/test_loan.py b/lending/loan_management/doctype/loan/test_loan.py
index ed1c2de9..f9907e40 100644
--- a/lending/loan_management/doctype/loan/test_loan.py
+++ b/lending/loan_management/doctype/loan/test_loan.py
@@ -24,6 +24,9 @@
unpledge_security,
)
from lending.loan_management.doctype.loan_application.loan_application import create_pledge
+from lending.loan_management.doctype.loan_collateral_deassignment.loan_collateral_deassignment import (
+ get_pledged_security_qty,
+)
from lending.loan_management.doctype.loan_disbursement.loan_disbursement import (
get_disbursal_amount,
)
@@ -31,9 +34,6 @@
days_in_year,
)
from lending.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts
-from lending.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import (
- get_pledged_security_qty,
-)
from lending.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import (
process_loan_interest_accrual_for_demand_loans,
process_loan_interest_accrual_for_term_loans,
@@ -222,7 +222,9 @@ def test_sanctioned_amount_limit(self):
# Clear loan docs before checking
frappe.db.sql("DELETE FROM `tabLoan` where applicant = '_Test Loan Customer 1'")
frappe.db.sql("DELETE FROM `tabLoan Application` where applicant = '_Test Loan Customer 1'")
- frappe.db.sql("DELETE FROM `tabLoan Security Pledge` where applicant = '_Test Loan Customer 1'")
+ frappe.db.sql(
+ "DELETE FROM `tabLoan Collateral Assignment` where applicant = '_Test Loan Customer 1'"
+ )
if not frappe.db.get_value(
"Sanctioned Loan Amount",
@@ -458,7 +460,7 @@ def test_security_shortfall(self):
self.assertEqual(loan_security_shortfall.status, "Completed")
self.assertEqual(loan_security_shortfall.shortfall_amount, 0)
- def test_loan_security_unpledge(self):
+ def test_loan_collateral_deassignment(self):
pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
loan_application = create_loan_application(
@@ -515,7 +517,7 @@ def test_loan_security_unpledge(self):
self.assertEqual(amounts["payable_principal_amount"], 0.0)
self.assertEqual(amounts["interest_amount"], 0)
- def test_partial_loan_security_unpledge(self):
+ def test_partial_loan_collateral_deassignment(self):
pledge = [
{"loan_security": "Test Security 1", "qty": 2000.00},
{"loan_security": "Test Security 2", "qty": 4000.00},
@@ -554,7 +556,7 @@ def test_partial_loan_security_unpledge(self):
unpledge_request.load_from_db()
self.assertEqual(unpledge_request.docstatus, 1)
- def test_sanctioned_loan_security_unpledge(self):
+ def test_sanctioned_loan_collateral_deassignment(self):
pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
loan_application = create_loan_application(
@@ -1144,9 +1146,9 @@ def create_loan_security():
).insert(ignore_permissions=True)
-def create_loan_security_pledge(applicant, pledges, loan_application=None, loan=None):
+def create_loan_collateral_assignment(applicant, pledges, loan_application=None, loan=None):
- lsp = frappe.new_doc("Loan Security Pledge")
+ lsp = frappe.new_doc("Loan Collateral Assignment")
lsp.applicant_type = "Customer"
lsp.applicant = applicant
lsp.company = "_Test Company"
@@ -1237,6 +1239,7 @@ def create_loan_application(
loan_application.loan_product = loan_product
loan_application.posting_date = posting_date or nowdate()
loan_application.is_secured_loan = 1
+ loan_application.collateral_type = "Loan Security"
if repayment_method:
loan_application.repayment_method = repayment_method
@@ -1307,6 +1310,7 @@ def create_loan_with_security(
"loan_product": loan_product,
"is_term_loan": 1,
"is_secured_loan": 1,
+ "collateral_type": "Loan Security",
"repayment_method": repayment_method,
"repayment_periods": repayment_periods,
"repayment_start_date": repayment_start_date or nowdate(),
@@ -1336,6 +1340,7 @@ def create_demand_loan(applicant, loan_product, loan_application, posting_date=N
"loan_product": loan_product,
"is_term_loan": 0,
"is_secured_loan": 1,
+ "collateral_type": "Loan Security",
"mode_of_payment": frappe.db.get_value("Mode of Payment", {"type": "Cash"}, "name"),
"payment_account": "Payment Account - _TC",
"loan_account": "Loan Account - _TC",
diff --git a/lending/loan_management/doctype/loan_application/loan_application.js b/lending/loan_management/doctype/loan_application/loan_application.js
index 96136cee..10742586 100644
--- a/lending/loan_management/doctype/loan_application/loan_application.js
+++ b/lending/loan_management/doctype/loan_application/loan_application.js
@@ -8,7 +8,7 @@ frappe.ui.form.on('Loan Application', {
setup: function(frm) {
frm.make_methods = {
'Loan': function() { frm.trigger('create_loan') },
- 'Loan Security Pledge': function() { frm.trigger('create_loan_security_pledge') },
+ 'Loan Collateral Assignment': function() { frm.trigger('create_loan_collateral_assignment') },
}
},
refresh: function(frm) {
@@ -39,10 +39,10 @@ frappe.ui.form.on('Loan Application', {
if (frm.doc.status == "Approved") {
if (frm.doc.is_secured_loan) {
- frappe.db.get_value("Loan Security Pledge", {"loan_application": frm.doc.name, "docstatus": 1}, "name", (r) => {
+ frappe.db.get_value("Loan Collateral Assignment", {"loan_application": frm.doc.name, "docstatus": 1}, "name", (r) => {
if (Object.keys(r).length === 0) {
- frm.add_custom_button(__('Loan Security Pledge'), function() {
- frm.trigger('create_loan_security_pledge');
+ frm.add_custom_button(__('Loan Collateral Assignment'), function() {
+ frm.trigger('create_loan_collateral_assignment');
},__('Create'))
}
});
@@ -69,10 +69,10 @@ frappe.ui.form.on('Loan Application', {
frm: frm
});
},
- create_loan_security_pledge: function(frm) {
+ create_loan_collateral_assignment: function(frm) {
if(!frm.doc.is_secured_loan) {
- frappe.throw(__("Loan Security Pledge can only be created for secured loans"));
+ frappe.throw(__("Loan Collateral Assignment can only be created for secured loans"));
}
frappe.call({
@@ -81,7 +81,7 @@ frappe.ui.form.on('Loan Application', {
loan_application: frm.doc.name
},
callback: function(r) {
- frappe.set_route("Form", "Loan Security Pledge", r.message);
+ frappe.set_route("Form", "Loan Collateral Assignment", r.message);
}
})
},
@@ -89,9 +89,6 @@ frappe.ui.form.on('Loan Application', {
frm.set_df_property('repayment_method', 'hidden', 1 - frm.doc.is_term_loan);
frm.set_df_property('repayment_method', 'reqd', frm.doc.is_term_loan);
},
- is_secured_loan: function(frm) {
- frm.set_df_property('proposed_pledges', 'reqd', frm.doc.is_secured_loan);
- },
calculate_amounts: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
diff --git a/lending/loan_management/doctype/loan_application/loan_application.json b/lending/loan_management/doctype/loan_application/loan_application.json
index b2089cac..8619560a 100644
--- a/lending/loan_management/doctype/loan_application/loan_application.json
+++ b/lending/loan_management/doctype/loan_application/loan_application.json
@@ -17,12 +17,16 @@
"loan_product",
"is_term_loan",
"loan_amount",
- "is_secured_loan",
"rate_of_interest",
+ "is_secured_loan",
+ "collateral_type",
"column_break_7",
"description",
"loan_security_details_section",
"proposed_pledges",
+ "loan_collateral_details_section",
+ "proposed_collaterals",
+ "section_break_rutm",
"maximum_loan_amount",
"repayment_info",
"repayment_method",
@@ -184,16 +188,17 @@
"label": "Is Secured Loan"
},
{
- "depends_on": "eval:doc.is_secured_loan == 1",
+ "depends_on": "eval:doc.collateral_type == \"Loan Security\"",
"fieldname": "loan_security_details_section",
"fieldtype": "Section Break",
"label": "Loan Security Details"
},
{
- "depends_on": "eval:doc.is_secured_loan == 1",
+ "depends_on": "eval:doc.collateral_type == \"Loan Security\"",
"fieldname": "proposed_pledges",
"fieldtype": "Table",
"label": "Proposed Pledges",
+ "mandatory_depends_on": "eval:doc.collateral_type == \"Loan Security\"",
"options": "Proposed Pledge"
},
{
@@ -210,15 +215,42 @@
"fieldtype": "Check",
"label": "Is Term Loan",
"read_only": 1
+ },
+ {
+ "depends_on": "eval:doc.is_secured_loan",
+ "fieldname": "collateral_type",
+ "fieldtype": "Select",
+ "label": "Collateral Type",
+ "mandatory_depends_on": "eval:doc.is_secured_loan",
+ "options": "\nLoan Security\nLoan Collateral"
+ },
+ {
+ "depends_on": "eval:doc.collateral_type == \"Loan Collateral\"",
+ "fieldname": "loan_collateral_details_section",
+ "fieldtype": "Section Break",
+ "label": "Loan Collateral Details"
+ },
+ {
+ "fieldname": "section_break_rutm",
+ "fieldtype": "Section Break"
+ },
+ {
+ "depends_on": "eval:doc.collateral_type == \"Loan Collateral\"",
+ "fieldname": "proposed_collaterals",
+ "fieldtype": "Table",
+ "label": "Proposed Collaterals",
+ "mandatory_depends_on": "eval:doc.collateral_type == \"Loan Collateral\"",
+ "options": "Loan Collateral Assignment Loan Collateral"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-10-02 22:14:22.606618",
+ "modified": "2023-10-19 04:46:39.891046",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Application",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
diff --git a/lending/loan_management/doctype/loan_application/loan_application.py b/lending/loan_management/doctype/loan_application/loan_application.py
index 067f03c4..91ee7a54 100644
--- a/lending/loan_management/doctype/loan_application/loan_application.py
+++ b/lending/loan_management/doctype/loan_application/loan_application.py
@@ -143,15 +143,27 @@ def calculate_payable_amount(self):
self.total_payable_amount = self.loan_amount + self.total_payable_interest
def set_loan_amount(self):
- if self.is_secured_loan and not self.proposed_pledges:
- frappe.throw(_("Proposed Pledges are mandatory for secured Loans"))
-
- if self.is_secured_loan and self.proposed_pledges:
- self.maximum_loan_amount = 0
- for security in self.proposed_pledges:
- self.maximum_loan_amount += flt(security.post_haircut_amount)
-
- if not self.loan_amount and self.is_secured_loan and self.proposed_pledges:
+ if self.is_secured_loan:
+ if self.collateral_type == "Loan Security" and not self.proposed_pledges:
+ frappe.throw(_("Proposed Pledges are mandatory for secured Loans"))
+ if self.collateral_type == "Loan Collateral" and not self.proposed_collaterals:
+ frappe.throw(_("Proposed Collaterals are mandatory for secured Loans"))
+
+ if self.is_secured_loan:
+ if self.proposed_pledges:
+ self.maximum_loan_amount = 0
+ for security in self.proposed_pledges:
+ self.maximum_loan_amount += flt(security.post_haircut_amount)
+ else:
+ self.maximum_loan_amount = 0
+ for collateral in self.proposed_collaterals:
+ self.maximum_loan_amount += flt(collateral.available_collateral_value)
+
+ if (
+ not self.loan_amount
+ and self.is_secured_loan
+ and (self.proposed_pledges or self.proposed_collaterals)
+ ):
self.loan_amount = self.maximum_loan_amount
@@ -203,31 +215,42 @@ def update_accounts(source_doc, target_doc, source_parent):
def create_pledge(loan_application, loan=None):
loan_application_doc = frappe.get_doc("Loan Application", loan_application)
- lsp = frappe.new_doc("Loan Security Pledge")
+ lsp = frappe.new_doc("Loan Collateral Assignment")
lsp.applicant_type = loan_application_doc.applicant_type
lsp.applicant = loan_application_doc.applicant
lsp.loan_application = loan_application_doc.name
lsp.company = loan_application_doc.company
+ lsp.collateral_type = loan_application_doc.collateral_type
if loan:
lsp.loan = loan
- for pledge in loan_application_doc.proposed_pledges:
-
- lsp.append(
- "securities",
- {
- "loan_security": pledge.loan_security,
- "qty": pledge.qty,
- "loan_security_price": pledge.loan_security_price,
- "haircut": pledge.haircut,
- },
- )
+ if lsp.collateral_type == "Loan Security":
+ for security in loan_application_doc.proposed_pledges:
+ lsp.append(
+ "securities",
+ {
+ "loan_security": security.loan_security,
+ "qty": security.qty,
+ "loan_security_price": security.loan_security_price,
+ "haircut": security.haircut,
+ },
+ )
+ else:
+ for collateral in loan_application_doc.proposed_collaterals:
+ lsp.append(
+ "collaterals",
+ {
+ "loan_security": collateral.loan_collateral,
+ "loan_collateral_name": collateral.loan_collateral_name,
+ "available_collateral_value": collateral.available_collateral_value,
+ },
+ )
lsp.save()
lsp.submit()
- message = _("Loan Security Pledge Created : {0}").format(lsp.name)
+ message = _("Loan Collateral Assignment Created : {0}").format(lsp.name)
frappe.msgprint(message)
return lsp.name
diff --git a/lending/loan_management/doctype/loan_application/loan_application_dashboard.py b/lending/loan_management/doctype/loan_application/loan_application_dashboard.py
index 1d90e9bb..da87e798 100644
--- a/lending/loan_management/doctype/loan_application/loan_application_dashboard.py
+++ b/lending/loan_management/doctype/loan_application/loan_application_dashboard.py
@@ -2,6 +2,6 @@ def get_data():
return {
"fieldname": "loan_application",
"transactions": [
- {"items": ["Loan", "Loan Security Pledge"]},
+ {"items": ["Loan", "Loan Collateral Assignment"]},
],
}
diff --git a/lending/loan_management/doctype/loan_security_pledge/__init__.py b/lending/loan_management/doctype/loan_collateral/__init__.py
similarity index 100%
rename from lending/loan_management/doctype/loan_security_pledge/__init__.py
rename to lending/loan_management/doctype/loan_collateral/__init__.py
diff --git a/lending/loan_management/doctype/loan_collateral/loan_collateral.js b/lending/loan_management/doctype/loan_collateral/loan_collateral.js
new file mode 100644
index 00000000..5b4daea3
--- /dev/null
+++ b/lending/loan_management/doctype/loan_collateral/loan_collateral.js
@@ -0,0 +1,26 @@
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Loan Collateral', {
+ refresh: function(frm) {
+ if (frm.doc.status === "Hypothecated") {
+ frm.add_custom_button(__("Release"), function() {
+ frm.trigger("release_loan_collateral");
+ })
+ }
+ },
+
+ release_loan_collateral: function(frm) {
+ frappe.confirm(__("Do you really want to release this loan collateral?"), function () {
+ frappe.call({
+ args: {
+ "loan_collateral": frm.doc.name,
+ },
+ method: "lending.loan_management.doctype.loan_collateral.loan_collateral.release_loan_collateral",
+ callback: function(r) {
+ cur_frm.reload_doc();
+ }
+ })
+ })
+ },
+});
\ No newline at end of file
diff --git a/lending/loan_management/doctype/loan_collateral/loan_collateral.json b/lending/loan_management/doctype/loan_collateral/loan_collateral.json
new file mode 100644
index 00000000..b84a378e
--- /dev/null
+++ b/lending/loan_management/doctype/loan_collateral/loan_collateral.json
@@ -0,0 +1,154 @@
+{
+ "actions": [],
+ "autoname": "field:collateral_code",
+ "creation": "2023-10-18 22:17:56.180433",
+ "default_view": "List",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "collateral_code",
+ "collateral_name",
+ "status",
+ "collateral_value",
+ "available_collateral_value",
+ "utilized_collateral_value",
+ "column_break_njyy",
+ "loan_applicant_type",
+ "loan_applicant",
+ "collateral_owner_type",
+ "collateral_owner",
+ "amended_from",
+ "released_date",
+ "disabled"
+ ],
+ "fields": [
+ {
+ "fieldname": "collateral_code",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Collateral Code",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "fieldname": "amended_from",
+ "fieldtype": "Link",
+ "label": "Amended From",
+ "no_copy": 1,
+ "options": "Loan Collateral",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "collateral_name",
+ "fieldtype": "Data",
+ "label": "Collateral Name",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "fieldname": "column_break_njyy",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "Pending Hypothecation",
+ "depends_on": "eval:!doc.__islocal",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "label": "Status",
+ "options": "Pending Hypothecation\nHypothecated\nReleased\nRepossessed",
+ "read_only": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "default": "0",
+ "depends_on": "eval:!doc.__islocal",
+ "fieldname": "disabled",
+ "fieldtype": "Check",
+ "label": "Disabled"
+ },
+ {
+ "fieldname": "collateral_value",
+ "fieldtype": "Currency",
+ "label": "Collateral Value",
+ "options": "Company:company:default_currency",
+ "reqd": 1
+ },
+ {
+ "fieldname": "available_collateral_value",
+ "fieldtype": "Currency",
+ "label": "Available Collateral Value",
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "utilized_collateral_value",
+ "fieldtype": "Currency",
+ "label": "Utilized Collateral Value",
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "loan_applicant_type",
+ "fieldtype": "Select",
+ "label": "Loan Applicant Type",
+ "options": "Employee\nMember\nCustomer",
+ "reqd": 1
+ },
+ {
+ "fieldname": "loan_applicant",
+ "fieldtype": "Dynamic Link",
+ "label": "Loan Applicant",
+ "options": "loan_applicant_type",
+ "reqd": 1
+ },
+ {
+ "fieldname": "collateral_owner_type",
+ "fieldtype": "Select",
+ "label": "Collateral Owner Type",
+ "options": "Employee\nMember\nCustomer\nLoan Collateral Third Party Owner\nCompany",
+ "reqd": 1
+ },
+ {
+ "fieldname": "collateral_owner",
+ "fieldtype": "Dynamic Link",
+ "label": "Collateral Owner",
+ "options": "collateral_owner_type",
+ "reqd": 1
+ },
+ {
+ "fieldname": "released_date",
+ "fieldtype": "Date",
+ "label": "Released Date",
+ "read_only": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2023-10-18 22:48:08.914757",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Loan Collateral",
+ "naming_rule": "By fieldname",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/lending/loan_management/doctype/loan_collateral/loan_collateral.py b/lending/loan_management/doctype/loan_collateral/loan_collateral.py
new file mode 100644
index 00000000..d8732841
--- /dev/null
+++ b/lending/loan_management/doctype/loan_collateral/loan_collateral.py
@@ -0,0 +1,278 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import itertools
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+from frappe.utils import flt, nowdate
+
+from lending.loan_management.doctype.loan_collateral_values_log.loan_collateral_values_log import (
+ create_loan_collateral_values_log,
+)
+
+
+class LoanCollateral(Document):
+ def validate(self):
+ self.set_available_collateral_value()
+
+ def set_available_collateral_value(self):
+ self.available_collateral_value = self.collateral_value
+
+
+def update_loan_collaterals_values(
+ loan,
+ amount,
+ trigger_doctype,
+ trigger_doc,
+ disbursement=False,
+ repayment=False,
+ on_trigger_doc_cancel=0,
+):
+ if (disbursement and repayment) or (not disbursement and not repayment):
+ frappe.throw(_("The action needs to be either disbursement or repayment"))
+
+ if not frappe.db.get_value("Loan", loan, "is_secured_loan"):
+ return
+
+ utilized_value_increased = (
+ True
+ if (disbursement and not on_trigger_doc_cancel) or (repayment and on_trigger_doc_cancel)
+ else False
+ )
+
+ sorted_loan_collaterals = _get_sorted_loan_collaterals_to_update_values(
+ loan, utilized_value_increased
+ )
+
+ _update_loan_collaterals_values(
+ sorted_loan_collaterals,
+ amount,
+ utilized_value_increased,
+ trigger_doctype,
+ trigger_doc,
+ on_trigger_doc_cancel,
+ )
+
+
+def _get_sorted_loan_collaterals_to_update_values(loan, utilized_value_increased):
+ loan_collaterals_w_ratio = []
+
+ for loan_collateral_assignment in frappe.db.get_all(
+ "Loan Collateral Assignment", {"loan": loan}, pluck="name"
+ ):
+ loan_collaterals = frappe.db.get_all(
+ "Loan Collateral Assignment Loan Collateral",
+ {"parent": loan_collateral_assignment},
+ pluck="loan_collateral",
+ )
+
+ for loan_collateral in loan_collaterals:
+ (
+ collateral_value,
+ available_collateral_value,
+ utilized_collateral_value,
+ ) = frappe.db.get_value(
+ "Loan Collateral",
+ loan_collateral,
+ [
+ "collateral_value",
+ "available_collateral_value",
+ "utilized_collateral_value",
+ ],
+ )
+
+ utilized_to_original_value_ratio = flt(utilized_collateral_value / collateral_value)
+
+ loan_collaterals_w_ratio.append(
+ frappe._dict(
+ {
+ "loan_collateral": loan_collateral,
+ "collateral_value": collateral_value,
+ "utilized_collateral_value": utilized_collateral_value,
+ "available_collateral_value": available_collateral_value,
+ "ratio": utilized_to_original_value_ratio,
+ }
+ )
+ )
+
+ sorted_loan_collaterals = sorted(
+ loan_collaterals_w_ratio,
+ key=lambda k: k["ratio"],
+ reverse=utilized_value_increased,
+ )
+
+ return sorted_loan_collaterals
+
+
+def _update_loan_collaterals_values(
+ sorted_loan_collaterals,
+ amount,
+ utilized_value_increased,
+ trigger_doctype,
+ trigger_doc,
+ on_trigger_doc_cancel,
+):
+ for loan_collateral in sorted_loan_collaterals:
+ if amount <= 0:
+ break
+
+ if utilized_value_increased:
+ if loan_collateral.utilized_collateral_value + amount > loan_collateral.collateral_value:
+ new_utilized_collateral_value = loan_collateral.collateral_value
+ new_available_collateral_value = 0
+ amount = amount + loan_collateral.utilized_collateral_value - loan_collateral.collateral_value
+ else:
+ new_utilized_collateral_value = loan_collateral.utilized_collateral_value + amount
+ new_available_collateral_value = loan_collateral.available_collateral_value - amount
+ amount = 0
+ else:
+ if loan_collateral.available_collateral_value + amount > loan_collateral.collateral_value:
+ new_available_collateral_value = loan_collateral.collateral_value
+ new_utilized_collateral_value = 0
+ amount = amount + loan_collateral.available_collateral_value - loan_collateral.collateral_value
+ else:
+ new_utilized_collateral_value = loan_collateral.utilized_collateral_value - amount
+ new_available_collateral_value = loan_collateral.available_collateral_value + amount
+ amount = 0
+
+ frappe.db.set_value(
+ "Loan Collateral",
+ loan_collateral.loan_collateral,
+ {
+ "utilized_collateral_value": new_utilized_collateral_value,
+ "available_collateral_value": new_available_collateral_value,
+ },
+ )
+
+ create_loan_collateral_values_log(
+ loan_collateral=loan_collateral.loan_collateral,
+ trigger_doctype=trigger_doctype,
+ trigger_document=trigger_doc,
+ on_trigger_doc_cancel=on_trigger_doc_cancel,
+ new_available_collateral_value=new_available_collateral_value,
+ new_utilized_collateral_value=new_utilized_collateral_value,
+ previous_available_collateral_value=loan_collateral.available_collateral_value,
+ previous_utilized_collateral_value=loan_collateral.utilized_collateral_value,
+ )
+
+
+@frappe.whitelist()
+def release_loan_collateral(loan_collateral):
+ active_loan_collaterals = get_active_loan_collaterals(loan_collateral)
+
+ if active_loan_collaterals:
+ msg = _("Loan Collateral {0} is linked with active loans:").format(frappe.bold(loan_collateral))
+ for loan_and_pledge in active_loan_collaterals:
+ msg += "
"
+ msg += _("Loan {0} through Loan Collateral Assignment {1}").format(
+ frappe.bold(loan_and_pledge.loan), frappe.bold(loan_and_pledge.pledge)
+ )
+ frappe.throw(msg, title=_("Loan Collateral cannot be released"))
+ else:
+ frappe.db.set_value(
+ "Loan Collateral", loan_collateral, {"status": "Released", "released_date": nowdate()}
+ )
+
+
+def get_active_loan_collaterals(loan_collateral):
+ active_loan_collaterals = []
+
+ lcalcs = frappe.db.sql(
+ """
+ SELECT lp.loan, lp.name as pledge
+ FROM `tabLoan Collateral Assignment` lp, `tabLoan Collateral Assignment Loan Collateral` p
+ WHERE p.loan_collateral = %s
+ AND p.parent = lp.name
+ AND lp.status = 'Assigned'
+ """,
+ (loan_collateral),
+ as_dict=True,
+ )
+
+ lcdcs = frappe.db.sql(
+ """
+ SELECT up.loan
+ FROM `tabLoan Collateral Deassignment` up, `tabLoan Collateral Deassignment Loan Collateral` u
+ WHERE u.loan_collateral = %s
+ AND u.parent = up.name
+ AND up.status = 'Approved'
+ """,
+ (loan_collateral),
+ as_list=True,
+ )
+ lcdcs = list(itertools.chain(*lcdcs))
+
+ for loan_and_pledge in lcalcs:
+ if loan_and_pledge.loan not in lcdcs:
+ active_loan_collaterals.append(loan_and_pledge)
+
+ return active_loan_collaterals
+
+
+def check_loan_collaterals_availability(loan, amount=None):
+ loan_amount, is_secured_loan = frappe.db.get_value(
+ "Loan", loan, ["loan_amount", "is_secured_loan"]
+ )
+
+ if not is_secured_loan:
+ return
+
+ amount = amount or loan_amount
+
+ total_available_collateral_value = 0
+
+ loan_collateral_assignments = frappe.db.get_all(
+ "Loan Collateral Assignment", {"loan": loan, "collateral_type": "Loan Collateral"}, pluck="name"
+ )
+
+ if not loan_collateral_assignments:
+ return
+
+ for loan_collateral_assignment in loan_collateral_assignments:
+ loan_collaterals = frappe.db.get_all(
+ "Loan Collateral Assignment Loan Collateral",
+ {"parent": loan_collateral_assignment},
+ pluck="loan_collateral",
+ )
+ for loan_collateral in loan_collaterals:
+ total_available_collateral_value += frappe.db.get_value(
+ "Loan Collateral", loan_collateral, "available_collateral_value"
+ )
+
+ if total_available_collateral_value < amount:
+ frappe.throw(
+ _("Loan Collaterals worth {0} needed more to book the loan.").format(
+ frappe.bold(amount - total_available_collateral_value),
+ )
+ )
+
+
+@frappe.whitelist()
+def get_pending_deassignment_collaterals(loan):
+ assignment_collaterals = frappe.db.sql(
+ """
+ SELECT lcalc.loan_collateral
+ FROM `tabLoan Collateral Assignment` lsp, `tabLoan Collateral Assignment Loan Collateral` lcalc
+ WHERE lsp.loan = %s
+ AND lcalc.parent = lsp.name
+ """,
+ (loan),
+ as_list=True,
+ )
+ assignment_collaterals = list(itertools.chain(*assignment_collaterals))
+
+ deassignment_collaterals = frappe.db.sql(
+ """
+ SELECT lcdlc.loan_collateral
+ FROM `tabLoan Collateral Deassignment` lsu, `tabLoan Collateral Deassignment Loan Collateral` lcdlc
+ WHERE lsu.loan = %s
+ AND lcdlc.parent = lsu.name
+ """,
+ (loan),
+ as_list=True,
+ )
+ deassignment_collaterals = list(itertools.chain(*deassignment_collaterals))
+
+ return list(set(assignment_collaterals) - set(deassignment_collaterals))
diff --git a/lending/loan_management/doctype/loan_collateral/loan_collateral_list.js b/lending/loan_management/doctype/loan_collateral/loan_collateral_list.js
new file mode 100644
index 00000000..319e409c
--- /dev/null
+++ b/lending/loan_management/doctype/loan_collateral/loan_collateral_list.js
@@ -0,0 +1,11 @@
+frappe.listview_settings['Loan Collateral'] = {
+ get_indicator: function(doc) {
+ var status_color = {
+ "Pending Hypothecation": "grey",
+ "Hypothecated": "blue",
+ "Released": "green",
+ "Repossessed": "red"
+ };
+ return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
+ },
+};
\ No newline at end of file
diff --git a/lending/loan_management/doctype/loan_collateral/test_loan_collateral.py b/lending/loan_management/doctype/loan_collateral/test_loan_collateral.py
new file mode 100644
index 00000000..7fb7f900
--- /dev/null
+++ b/lending/loan_management/doctype/loan_collateral/test_loan_collateral.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+from frappe.tests.utils import FrappeTestCase
+
+
+class TestLoanCollateral(FrappeTestCase):
+ pass
diff --git a/lending/loan_management/doctype/loan_security_unpledge/__init__.py b/lending/loan_management/doctype/loan_collateral_assignment/__init__.py
similarity index 100%
rename from lending/loan_management/doctype/loan_security_unpledge/__init__.py
rename to lending/loan_management/doctype/loan_collateral_assignment/__init__.py
diff --git a/lending/loan_management/doctype/loan_security_pledge/loan_security_pledge.js b/lending/loan_management/doctype/loan_collateral_assignment/loan_collateral_assignment.js
similarity index 50%
rename from lending/loan_management/doctype/loan_security_pledge/loan_security_pledge.js
rename to lending/loan_management/doctype/loan_collateral_assignment/loan_collateral_assignment.js
index e61868e8..dcb31ff9 100644
--- a/lending/loan_management/doctype/loan_security_pledge/loan_security_pledge.js
+++ b/lending/loan_management/doctype/loan_collateral_assignment/loan_collateral_assignment.js
@@ -1,8 +1,20 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Loan Security Pledge', {
- calculate_amounts: function(frm, cdt, cdn) {
+frappe.ui.form.on('Loan Collateral Assignment', {
+ loan: function(frm, cdt, cdn) {
+ frappe.db.get_value("Loan", frm.doc.loan, "collateral_type", (r) => {
+ frm.set_value('collateral_type', r.collateral_type);
+ });
+ },
+
+ loan_application: function(frm, cdt, cdn) {
+ frappe.db.get_value("Loan Application", frm.doc.loan_application, "collateral_type", (r) => {
+ frm.set_value('collateral_type', r.collateral_type);
+ });
+ },
+
+ calculate_loan_securities_amounts: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
frappe.model.set_value(cdt, cdn, 'amount', row.qty * row.loan_security_price);
frappe.model.set_value(cdt, cdn, 'post_haircut_amount', cint(row.amount - (row.amount * row.haircut/100)));
@@ -16,6 +28,16 @@ frappe.ui.form.on('Loan Security Pledge', {
frm.set_value('total_security_value', amount);
frm.set_value('maximum_loan_value', maximum_amount);
+ },
+
+ calculate_loan_collaterals_amounts: function(frm, cdt, cdn) {
+ let amount = 0;
+ $.each(frm.doc.collaterals || [], function(i, item){
+ amount += item.available_collateral_value;
+ });
+
+ frm.set_value('total_security_value', amount);
+ frm.set_value('maximum_loan_value', amount);
}
});
@@ -31,13 +53,19 @@ frappe.ui.form.on("Pledge", {
},
callback: function(r) {
frappe.model.set_value(cdt, cdn, 'loan_security_price', r.message);
- frm.events.calculate_amounts(frm, cdt, cdn);
+ frm.events.calculate_loan_securities_amounts(frm, cdt, cdn);
}
});
}
},
qty: function(frm, cdt, cdn) {
- frm.events.calculate_amounts(frm, cdt, cdn);
+ frm.events.calculate_loan_securities_amounts(frm, cdt, cdn);
+ },
+});
+
+frappe.ui.form.on("Loan Collateral Assignment Loan Collateral", {
+ loan_collateral: function(frm, cdt, cdn) {
+ frm.events.calculate_loan_collaterals_amounts(frm, cdt, cdn);
},
});
diff --git a/lending/loan_management/doctype/loan_security_pledge/loan_security_pledge.json b/lending/loan_management/doctype/loan_collateral_assignment/loan_collateral_assignment.json
similarity index 67%
rename from lending/loan_management/doctype/loan_security_pledge/loan_security_pledge.json
rename to lending/loan_management/doctype/loan_collateral_assignment/loan_collateral_assignment.json
index 68bac8ed..7a22eaf3 100644
--- a/lending/loan_management/doctype/loan_security_pledge/loan_security_pledge.json
+++ b/lending/loan_management/doctype/loan_collateral_assignment/loan_collateral_assignment.json
@@ -11,12 +11,15 @@
"applicant",
"loan",
"loan_application",
+ "collateral_type",
"column_break_3",
"company",
"pledge_time",
"status",
"loan_security_details_section",
"securities",
+ "loan_collateral_details_section",
+ "collaterals",
"section_break_10",
"total_security_value",
"column_break_11",
@@ -33,11 +36,9 @@
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
- "options": "Loan Security Pledge",
+ "options": "Loan Collateral Assignment",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fetch_from": "loan_application.applicant",
@@ -47,63 +48,48 @@
"in_standard_filter": 1,
"label": "Applicant",
"options": "applicant_type",
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
+ "depends_on": "eval:doc.collateral_type == \"Loan Security\"",
"fieldname": "loan_security_details_section",
"fieldtype": "Section Break",
- "label": "Loan Security Details",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Loan Security Details"
},
{
"fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "loan",
"fieldtype": "Link",
"label": "Loan",
- "options": "Loan",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Loan"
},
{
"fieldname": "loan_application",
"fieldtype": "Link",
"label": "Loan Application",
- "options": "Loan Application",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Loan Application"
},
{
"fieldname": "total_security_value",
"fieldtype": "Currency",
"label": "Total Security Value",
"options": "Company:company:default_currency",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "maximum_loan_value",
"fieldtype": "Currency",
"label": "Maximum Loan Value",
"options": "Company:company:default_currency",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "loan_details_section",
"fieldtype": "Section Break",
- "label": "Loan Details",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Loan Details"
},
{
"default": "Requested",
@@ -112,49 +98,38 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Status",
- "options": "Requested\nUnpledged\nPledged\nPartially Pledged\nCancelled",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "options": "Requested\nUnassigned\nAssigned\nCancelled",
+ "read_only": 1
},
{
"fieldname": "pledge_time",
"fieldtype": "Datetime",
"label": "Pledge Time",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
+ "depends_on": "eval:doc.collateral_type == \"Loan Security\"",
"fieldname": "securities",
"fieldtype": "Table",
"label": "Securities",
- "options": "Pledge",
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "mandatory_depends_on": "eval:doc.collateral_type == \"Loan Security\"",
+ "options": "Pledge"
},
{
"fieldname": "column_break_11",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "section_break_10",
"fieldtype": "Section Break",
- "label": "Totals",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Totals"
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"fetch_from": "loan.applicant_type",
@@ -162,48 +137,60 @@
"fieldtype": "Select",
"label": "Applicant Type",
"options": "Employee\nMember\nCustomer",
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"collapsible": 1,
"fieldname": "more_information_section",
"fieldtype": "Section Break",
- "label": "More Information",
- "show_days": 1,
- "show_seconds": 1
+ "label": "More Information"
},
{
"allow_on_submit": 1,
"fieldname": "reference_no",
"fieldtype": "Data",
- "label": "Reference No",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Reference No"
},
{
"fieldname": "column_break_18",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"allow_on_submit": 1,
"fieldname": "description",
"fieldtype": "Text",
- "label": "Description",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Description"
+ },
+ {
+ "fieldname": "collateral_type",
+ "fieldtype": "Select",
+ "label": "Collateral Type",
+ "options": "\nLoan Security\nLoan Collateral",
+ "read_only": 1
+ },
+ {
+ "depends_on": "eval:doc.collateral_type == \"Loan Collateral\"",
+ "fieldname": "loan_collateral_details_section",
+ "fieldtype": "Section Break",
+ "label": "Loan Collateral Details"
+ },
+ {
+ "depends_on": "eval:doc.collateral_type == \"Loan Collateral\"",
+ "fieldname": "collaterals",
+ "fieldtype": "Table",
+ "label": "Collaterals",
+ "mandatory_depends_on": "eval:doc.collateral_type == \"Loan Collateral\"",
+ "options": "Loan Collateral Assignment Loan Collateral"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-06-29 17:15:16.082256",
+ "modified": "2023-10-19 08:17:16.100919",
"modified_by": "Administrator",
"module": "Loan Management",
- "name": "Loan Security Pledge",
+ "name": "Loan Collateral Assignment",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -237,9 +224,9 @@
"write": 1
}
],
- "quick_entry": 1,
"search_fields": "applicant",
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/lending/loan_management/doctype/loan_security_pledge/loan_security_pledge.py b/lending/loan_management/doctype/loan_collateral_assignment/loan_collateral_assignment.py
similarity index 52%
rename from lending/loan_management/doctype/loan_security_pledge/loan_security_pledge.py
rename to lending/loan_management/doctype/loan_collateral_assignment/loan_collateral_assignment.py
index 5b68157a..31fe6f46 100644
--- a/lending/loan_management/doctype/loan_security_pledge/loan_security_pledge.py
+++ b/lending/loan_management/doctype/loan_collateral_assignment/loan_collateral_assignment.py
@@ -15,15 +15,24 @@
)
-class LoanSecurityPledge(Document):
+class LoanCollateralAssignment(Document):
def validate(self):
+ self.set_missing_values()
self.set_pledge_amount()
- self.validate_duplicate_securities()
+ self.validate_duplicate_securities_and_collaterals()
+ self.validate_loan_collaterals()
self.validate_loan_security_type()
+ def set_missing_values(self):
+ if not self.collateral_type:
+ if self.loan:
+ self.collateral_type = frappe.db.get_value("Loan", self.loan, "collateral_type")
+ elif self.loan_application:
+ self.collateral_type = frappe.db.get_value("Loan", self.loan_application, "collateral_type")
+
def on_submit(self):
if self.loan:
- self.db_set("status", "Pledged")
+ self.db_set("status", "Assigned")
self.db_set("pledge_time", now_datetime())
update_shortfall_status(self.loan, self.total_security_value)
update_loan(self.loan, self.maximum_loan_value)
@@ -34,7 +43,21 @@ def on_cancel(self):
self.db_set("pledge_time", None)
update_loan(self.loan, self.maximum_loan_value, cancel=1)
- def validate_duplicate_securities(self):
+ def validate_loan_collaterals(self):
+ for collateral in self.collaterals:
+ status, loan_applicant = frappe.db.get_value(
+ "Loan Collateral", collateral, ["status", "loan_applicant"]
+ )
+ if status in ["Released", "Repossessed"]:
+ frappe.throw(
+ _("Row {0}: released or repossessed collateral cannot be hypothecated").format(collateral.idx)
+ )
+ if loan_applicant != self.applicant:
+ frappe.throw(
+ _("Row {0}: collateral applicant does not match loan applicant").format(collateral.idx)
+ )
+
+ def validate_duplicate_securities_and_collaterals(self):
security_list = []
for security in self.securities:
if security.loan_security not in security_list:
@@ -44,12 +67,24 @@ def validate_duplicate_securities(self):
_("Loan Security {0} added multiple times").format(frappe.bold(security.loan_security))
)
+ collateral_list = []
+ for collateral in self.collaterals:
+ if collateral.loan_collateral not in collateral_list:
+ collateral_list.append(collateral.loan_collateral)
+ else:
+ frappe.throw(
+ _("Loan Collateral {0} added multiple times").format(frappe.bold(collateral.loan_collateral))
+ )
+
def validate_loan_security_type(self):
+ if not self.collateral_type == "Loan Security":
+ return
+
existing_pledge = ""
if self.loan:
existing_pledge = frappe.db.get_value(
- "Loan Security Pledge", {"loan": self.loan, "docstatus": 1}, ["name"]
+ "Loan Collateral Assignment", {"loan": self.loan, "docstatus": 1}, ["name"]
)
if existing_pledge:
@@ -73,22 +108,26 @@ def set_pledge_amount(self):
total_security_value = 0
maximum_loan_value = 0
- for pledge in self.securities:
-
- if not pledge.qty and not pledge.amount:
- frappe.throw(_("Qty or Amount is mandatory for loan security!"))
+ if self.collateral_type == "Loan Security":
+ for pledge in self.securities:
+ if not pledge.qty and not pledge.amount:
+ frappe.throw(_("Qty or Amount is mandatory for loan security!"))
- if not (self.loan_application and pledge.loan_security_price):
- pledge.loan_security_price = get_loan_security_price(pledge.loan_security)
+ if not (self.loan_application and pledge.loan_security_price):
+ pledge.loan_security_price = get_loan_security_price(pledge.loan_security)
- if not pledge.qty:
- pledge.qty = cint(pledge.amount / pledge.loan_security_price)
+ if not pledge.qty:
+ pledge.qty = cint(pledge.amount / pledge.loan_security_price)
- pledge.amount = pledge.qty * pledge.loan_security_price
- pledge.post_haircut_amount = cint(pledge.amount - (pledge.amount * pledge.haircut / 100))
+ pledge.amount = pledge.qty * pledge.loan_security_price
+ pledge.post_haircut_amount = cint(pledge.amount - (pledge.amount * pledge.haircut / 100))
- total_security_value += pledge.amount
- maximum_loan_value += pledge.post_haircut_amount
+ total_security_value += pledge.amount
+ maximum_loan_value += pledge.post_haircut_amount
+ else:
+ for collateral in self.collaterals:
+ total_security_value += collateral.available_collateral_value
+ maximum_loan_value = total_security_value
self.total_security_value = total_security_value
self.maximum_loan_value = maximum_loan_value
diff --git a/lending/loan_management/doctype/loan_security_pledge/loan_security_pledge_list.js b/lending/loan_management/doctype/loan_collateral_assignment/loan_collateral_assignment_list.js
similarity index 69%
rename from lending/loan_management/doctype/loan_security_pledge/loan_security_pledge_list.js
rename to lending/loan_management/doctype/loan_collateral_assignment/loan_collateral_assignment_list.js
index 174d1b0d..180cdc90 100644
--- a/lending/loan_management/doctype/loan_security_pledge/loan_security_pledge_list.js
+++ b/lending/loan_management/doctype/loan_collateral_assignment/loan_collateral_assignment_list.js
@@ -2,13 +2,12 @@
// License: GNU General Public License v3. See license.txt
// render
-frappe.listview_settings['Loan Security Pledge'] = {
+frappe.listview_settings['Loan Collateral Assignment'] = {
add_fields: ["status"],
get_indicator: function(doc) {
var status_color = {
- "Unpledged": "orange",
- "Pledged": "green",
- "Partially Pledged": "green"
+ "Unassigned": "orange",
+ "Assigned": "green",
};
return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
}
diff --git a/lending/loan_management/doctype/loan_security_pledge/test_loan_security_pledge.py b/lending/loan_management/doctype/loan_collateral_assignment/test_loan_collateral_assignment.py
similarity index 69%
rename from lending/loan_management/doctype/loan_security_pledge/test_loan_security_pledge.py
rename to lending/loan_management/doctype/loan_collateral_assignment/test_loan_collateral_assignment.py
index b9d05c28..93a91101 100644
--- a/lending/loan_management/doctype/loan_security_pledge/test_loan_security_pledge.py
+++ b/lending/loan_management/doctype/loan_collateral_assignment/test_loan_collateral_assignment.py
@@ -5,5 +5,5 @@
import unittest
-class TestLoanSecurityPledge(unittest.TestCase):
+class TestLoanCollateralAssignment(unittest.TestCase):
pass
diff --git a/lending/loan_management/doctype/loan_collateral_assignment_loan_collateral/__init__.py b/lending/loan_management/doctype/loan_collateral_assignment_loan_collateral/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/lending/loan_management/doctype/loan_collateral_assignment_loan_collateral/loan_collateral_assignment_loan_collateral.json b/lending/loan_management/doctype/loan_collateral_assignment_loan_collateral/loan_collateral_assignment_loan_collateral.json
new file mode 100644
index 00000000..f2842788
--- /dev/null
+++ b/lending/loan_management/doctype/loan_collateral_assignment_loan_collateral/loan_collateral_assignment_loan_collateral.json
@@ -0,0 +1,50 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2023-10-19 00:40:53.780767",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "loan_collateral",
+ "loan_collateral_name",
+ "available_collateral_value"
+ ],
+ "fields": [
+ {
+ "fieldname": "loan_collateral",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Loan Collateral",
+ "options": "Loan Collateral",
+ "reqd": 1
+ },
+ {
+ "fetch_from": "loan_collateral.collateral_name",
+ "fieldname": "loan_collateral_name",
+ "fieldtype": "Data",
+ "label": "Loan Collateral Name"
+ },
+ {
+ "fetch_from": "loan_collateral.available_collateral_value",
+ "fieldname": "available_collateral_value",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Available Collateral Value",
+ "options": "Company:company:default_currency",
+ "read_only": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2023-10-19 00:46:05.909347",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Loan Collateral Assignment Loan Collateral",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/lending/loan_management/doctype/loan_collateral_assignment_loan_collateral/loan_collateral_assignment_loan_collateral.py b/lending/loan_management/doctype/loan_collateral_assignment_loan_collateral/loan_collateral_assignment_loan_collateral.py
new file mode 100644
index 00000000..80cfa2c9
--- /dev/null
+++ b/lending/loan_management/doctype/loan_collateral_assignment_loan_collateral/loan_collateral_assignment_loan_collateral.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class LoanCollateralAssignmentLoanCollateral(Document):
+ pass
diff --git a/lending/loan_management/doctype/loan_collateral_deassignment/__init__.py b/lending/loan_management/doctype/loan_collateral_deassignment/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/lending/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.js b/lending/loan_management/doctype/loan_collateral_deassignment/loan_collateral_deassignment.js
similarity index 84%
rename from lending/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.js
rename to lending/loan_management/doctype/loan_collateral_deassignment/loan_collateral_deassignment.js
index 82232062..78e3be12 100644
--- a/lending/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.js
+++ b/lending/loan_management/doctype/loan_collateral_deassignment/loan_collateral_deassignment.js
@@ -1,7 +1,7 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Loan Security Unpledge', {
+frappe.ui.form.on('Loan Collateral Deassignment', {
refresh: function(frm) {
if (frm.doc.docstatus == 1 && frm.doc.status == 'Approved') {
diff --git a/lending/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json b/lending/loan_management/doctype/loan_collateral_deassignment/loan_collateral_deassignment.json
similarity index 73%
rename from lending/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json
rename to lending/loan_management/doctype/loan_collateral_deassignment/loan_collateral_deassignment.json
index 92923bbf..370560dc 100644
--- a/lending/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json
+++ b/lending/loan_management/doctype/loan_collateral_deassignment/loan_collateral_deassignment.json
@@ -10,12 +10,15 @@
"loan",
"applicant_type",
"applicant",
+ "collateral_type",
"column_break_3",
"company",
"unpledge_time",
"status",
"loan_security_details_section",
"securities",
+ "loan_collateral_details_section",
+ "collaterals",
"more_information_section",
"reference_no",
"column_break_13",
@@ -65,6 +68,7 @@
"read_only": 1
},
{
+ "depends_on": "eval:doc.collateral_type == \"Loan Security\"",
"fieldname": "loan_security_details_section",
"fieldtype": "Section Break",
"label": "Loan Security Details"
@@ -74,16 +78,17 @@
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
- "options": "Loan Security Unpledge",
+ "options": "Loan Collateral Deassignment",
"print_hide": 1,
"read_only": 1
},
{
+ "depends_on": "eval:doc.collateral_type == \"Loan Security\"",
"fieldname": "securities",
"fieldtype": "Table",
"label": "Securities",
- "options": "Unpledge",
- "reqd": 1
+ "mandatory_depends_on": "eval:doc.collateral_type == \"Loan Security\"",
+ "options": "Unpledge"
},
{
"fieldname": "company",
@@ -121,15 +126,39 @@
"fieldname": "description",
"fieldtype": "Text",
"label": "Description"
+ },
+ {
+ "fetch_from": "loan.collateral_type",
+ "fieldname": "collateral_type",
+ "fieldtype": "Select",
+ "label": "Collateral Type",
+ "options": "\nLoan Security\nLoan Collateral",
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "depends_on": "eval:doc.collateral_type == \"Loan Collateral\"",
+ "fieldname": "loan_collateral_details_section",
+ "fieldtype": "Section Break",
+ "label": "Loan Collateral Details"
+ },
+ {
+ "depends_on": "eval:doc.collateral_type == \"Loan Collateral\"",
+ "fieldname": "collaterals",
+ "fieldtype": "Table",
+ "label": "Collaterals",
+ "mandatory_depends_on": "eval:doc.collateral_type == \"Loan Collateral\"",
+ "options": "Loan Collateral Deassignment Loan Collateral"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-04-19 18:12:01.401744",
+ "modified": "2023-10-19 07:44:49.615145",
"modified_by": "Administrator",
"module": "Loan Management",
- "name": "Loan Security Unpledge",
+ "name": "Loan Collateral Deassignment",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -175,9 +204,9 @@
"write": 1
}
],
- "quick_entry": 1,
"search_fields": "applicant",
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/lending/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/lending/loan_management/doctype/loan_collateral_deassignment/loan_collateral_deassignment.py
similarity index 72%
rename from lending/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
rename to lending/loan_management/doctype/loan_collateral_deassignment/loan_collateral_deassignment.py
index a9edd3e0..95ee1938 100644
--- a/lending/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
+++ b/lending/loan_management/doctype/loan_collateral_deassignment/loan_collateral_deassignment.py
@@ -8,25 +8,33 @@
from frappe.utils import flt, get_datetime, getdate
-class LoanSecurityUnpledge(Document):
+class LoanCollateralDeassignment(Document):
def validate(self):
- self.validate_duplicate_securities()
+ self.validate_duplicate_securities_and_collaterals()
self.validate_unpledge_qty()
+ self.validate_unpledge_loan_collaterals()
def on_cancel(self):
self.update_loan_status(cancel=1)
self.db_set("status", "Requested")
- def validate_duplicate_securities(self):
+ def validate_duplicate_securities_and_collaterals(self):
security_list = []
- for d in self.securities:
- if d.loan_security not in security_list:
- security_list.append(d.loan_security)
+ for security in self.securities:
+ if security.loan_security not in security_list:
+ security_list.append(security.loan_security)
+ else:
+ frappe.throw(
+ _("Loan Security {0} added multiple times").format(frappe.bold(security.loan_security))
+ )
+
+ collateral_list = []
+ for collateral in self.collaterals:
+ if collateral.loan_collateral not in collateral_list:
+ collateral_list.append(collateral.loan_collateral)
else:
frappe.throw(
- _("Row {0}: Loan Security {1} added multiple times").format(
- d.idx, frappe.bold(d.loan_security)
- )
+ _("Loan Collateral {0} added multiple times").format(frappe.bold(collateral.loan_collateral))
)
def validate_unpledge_qty(self):
@@ -37,6 +45,9 @@ def validate_unpledge_qty(self):
get_ltv_ratio,
)
+ if self.collateral_type != "Loan Security":
+ return
+
pledge_qty_map = get_pledged_security_qty(self.loan)
ltv_ratio_map = frappe._dict(
@@ -88,7 +99,7 @@ def validate_unpledge_qty(self):
)
msg += "
"
msg += _("You are trying to unpledge more.")
- frappe.throw(msg, title=_("Loan Security Unpledge Error"))
+ frappe.throw(msg, title=_("Loan Collateral Deassignment Error"))
unpledge_qty_map.setdefault(security.loan_security, 0)
unpledge_qty_map[security.loan_security] += security.qty
@@ -115,6 +126,37 @@ def _throw(self, security_value, pending_principal_amount, ltv_ratio):
msg += _("Loan To Security Value ratio must always be {0}").format(frappe.bold(ltv_ratio))
frappe.throw(msg, title=_("Loan To Value ratio breach"))
+ def validate_unpledge_loan_collaterals(self):
+ from lending.loan_management.doctype.loan_repayment.loan_repayment import (
+ get_pending_principal_amount,
+ )
+
+ if self.collateral_type != "Loan Collateral":
+ return
+
+ loan_details = frappe.get_value(
+ "Loan",
+ self.loan,
+ [
+ "total_payment",
+ "debit_adjustment_amount",
+ "credit_adjustment_amount",
+ "refund_amount",
+ "total_principal_paid",
+ "loan_amount",
+ "total_interest_payable",
+ "written_off_amount",
+ "disbursed_amount",
+ "status",
+ ],
+ as_dict=1,
+ )
+
+ pending_principal_amount = get_pending_principal_amount(loan_details)
+
+ if flt(pending_principal_amount, 2) > 0:
+ frappe.throw(_("Loan Collaterals cannot be unpledged since there is pending principal amount."))
+
def on_update_after_submit(self):
self.approve()
@@ -148,7 +190,7 @@ def get_pledged_security_qty(loan):
frappe.db.sql(
"""
SELECT u.loan_security, sum(u.qty) as qty
- FROM `tabLoan Security Unpledge` up, `tabUnpledge` u
+ FROM `tabLoan Collateral Deassignment` up, `tabUnpledge` u
WHERE up.loan = %s
AND u.parent = up.name
AND up.status = 'Approved'
@@ -162,10 +204,10 @@ def get_pledged_security_qty(loan):
frappe.db.sql(
"""
SELECT p.loan_security, sum(p.qty) as qty
- FROM `tabLoan Security Pledge` lp, `tabPledge`p
+ FROM `tabLoan Collateral Assignment` lp, `tabPledge`p
WHERE lp.loan = %s
AND p.parent = lp.name
- AND lp.status = 'Pledged'
+ AND lp.status = 'Assigned'
GROUP BY p.loan_security
""",
(loan),
diff --git a/lending/loan_management/doctype/loan_security_unpledge/loan_security_unpledge_list.js b/lending/loan_management/doctype/loan_collateral_deassignment/loan_collateral_deassignment_list.js
similarity index 85%
rename from lending/loan_management/doctype/loan_security_unpledge/loan_security_unpledge_list.js
rename to lending/loan_management/doctype/loan_collateral_deassignment/loan_collateral_deassignment_list.js
index 196ebbb9..32a6db5b 100644
--- a/lending/loan_management/doctype/loan_security_unpledge/loan_security_unpledge_list.js
+++ b/lending/loan_management/doctype/loan_collateral_deassignment/loan_collateral_deassignment_list.js
@@ -2,7 +2,7 @@
// License: GNU General Public License v3. See license.txt
// render
-frappe.listview_settings['Loan Security Unpledge'] = {
+frappe.listview_settings['Loan Collateral Deassignment'] = {
add_fields: ["status"],
get_indicator: function(doc) {
var status_color = {
diff --git a/lending/loan_management/doctype/loan_security_unpledge/test_loan_security_unpledge.py b/lending/loan_management/doctype/loan_collateral_deassignment/test_loan_collateral_deassignment.py
similarity index 69%
rename from lending/loan_management/doctype/loan_security_unpledge/test_loan_security_unpledge.py
rename to lending/loan_management/doctype/loan_collateral_deassignment/test_loan_collateral_deassignment.py
index 2f124e49..5516c58b 100644
--- a/lending/loan_management/doctype/loan_security_unpledge/test_loan_security_unpledge.py
+++ b/lending/loan_management/doctype/loan_collateral_deassignment/test_loan_collateral_deassignment.py
@@ -5,5 +5,5 @@
import unittest
-class TestLoanSecurityUnpledge(unittest.TestCase):
+class TestLoanCollateralDeassignment(unittest.TestCase):
pass
diff --git a/lending/loan_management/doctype/loan_collateral_deassignment_loan_collateral/__init__.py b/lending/loan_management/doctype/loan_collateral_deassignment_loan_collateral/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/lending/loan_management/doctype/loan_collateral_deassignment_loan_collateral/loan_collateral_deassignment_loan_collateral.json b/lending/loan_management/doctype/loan_collateral_deassignment_loan_collateral/loan_collateral_deassignment_loan_collateral.json
new file mode 100644
index 00000000..4ab0934c
--- /dev/null
+++ b/lending/loan_management/doctype/loan_collateral_deassignment_loan_collateral/loan_collateral_deassignment_loan_collateral.json
@@ -0,0 +1,40 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2023-10-19 03:33:30.702413",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "loan_collateral",
+ "loan_collateral_name"
+ ],
+ "fields": [
+ {
+ "fieldname": "loan_collateral",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Loan Collateral",
+ "options": "Loan Collateral",
+ "reqd": 1
+ },
+ {
+ "fetch_from": "loan_collateral.collateral_name",
+ "fieldname": "loan_collateral_name",
+ "fieldtype": "Data",
+ "label": "Loan Collateral Name"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2023-10-19 03:33:49.442060",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Loan Collateral Deassignment Loan Collateral",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/lending/loan_management/doctype/loan_collateral_deassignment_loan_collateral/loan_collateral_deassignment_loan_collateral.py b/lending/loan_management/doctype/loan_collateral_deassignment_loan_collateral/loan_collateral_deassignment_loan_collateral.py
new file mode 100644
index 00000000..125a365e
--- /dev/null
+++ b/lending/loan_management/doctype/loan_collateral_deassignment_loan_collateral/loan_collateral_deassignment_loan_collateral.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class LoanCollateralDeassignmentLoanCollateral(Document):
+ pass
diff --git a/lending/loan_management/doctype/loan_collateral_third_party_owner/__init__.py b/lending/loan_management/doctype/loan_collateral_third_party_owner/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/lending/loan_management/doctype/loan_collateral_third_party_owner/loan_collateral_third_party_owner.js b/lending/loan_management/doctype/loan_collateral_third_party_owner/loan_collateral_third_party_owner.js
new file mode 100644
index 00000000..cb5c31b6
--- /dev/null
+++ b/lending/loan_management/doctype/loan_collateral_third_party_owner/loan_collateral_third_party_owner.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+// frappe.ui.form.on("Loan Collateral Third Party Owner", {
+// refresh(frm) {
+
+// },
+// });
diff --git a/lending/loan_management/doctype/loan_collateral_third_party_owner/loan_collateral_third_party_owner.json b/lending/loan_management/doctype/loan_collateral_third_party_owner/loan_collateral_third_party_owner.json
new file mode 100644
index 00000000..ac98a2b4
--- /dev/null
+++ b/lending/loan_management/doctype/loan_collateral_third_party_owner/loan_collateral_third_party_owner.json
@@ -0,0 +1,55 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "field:collateral_third_party_owner_code",
+ "creation": "2023-10-18 22:42:55.359452",
+ "default_view": "List",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "collateral_third_party_owner_code",
+ "collateral_third_party_owner_name"
+ ],
+ "fields": [
+ {
+ "fieldname": "collateral_third_party_owner_code",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Collateral Third Party Owner Code",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "fieldname": "collateral_third_party_owner_name",
+ "fieldtype": "Date",
+ "label": "Collateral Third Party Owner Name",
+ "reqd": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2023-10-18 22:43:12.150354",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Loan Collateral Third Party Owner",
+ "naming_rule": "By fieldname",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/lending/loan_management/doctype/loan_collateral_third_party_owner/loan_collateral_third_party_owner.py b/lending/loan_management/doctype/loan_collateral_third_party_owner/loan_collateral_third_party_owner.py
new file mode 100644
index 00000000..c4d222a2
--- /dev/null
+++ b/lending/loan_management/doctype/loan_collateral_third_party_owner/loan_collateral_third_party_owner.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class LoanCollateralThirdPartyOwner(Document):
+ pass
diff --git a/lending/loan_management/doctype/loan_collateral_third_party_owner/test_loan_collateral_third_party_owner.py b/lending/loan_management/doctype/loan_collateral_third_party_owner/test_loan_collateral_third_party_owner.py
new file mode 100644
index 00000000..c0da3be8
--- /dev/null
+++ b/lending/loan_management/doctype/loan_collateral_third_party_owner/test_loan_collateral_third_party_owner.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+from frappe.tests.utils import FrappeTestCase
+
+
+class TestLoanCollateralThirdPartyOwner(FrappeTestCase):
+ pass
diff --git a/lending/loan_management/doctype/loan_collateral_values_log/__init__.py b/lending/loan_management/doctype/loan_collateral_values_log/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/lending/loan_management/doctype/loan_collateral_values_log/loan_collateral_values_log.js b/lending/loan_management/doctype/loan_collateral_values_log/loan_collateral_values_log.js
new file mode 100644
index 00000000..e5618a86
--- /dev/null
+++ b/lending/loan_management/doctype/loan_collateral_values_log/loan_collateral_values_log.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+// frappe.ui.form.on("Loan Collateral Values Log", {
+// refresh(frm) {
+
+// },
+// });
diff --git a/lending/loan_management/doctype/loan_collateral_values_log/loan_collateral_values_log.json b/lending/loan_management/doctype/loan_collateral_values_log/loan_collateral_values_log.json
new file mode 100644
index 00000000..f3bf2e47
--- /dev/null
+++ b/lending/loan_management/doctype/loan_collateral_values_log/loan_collateral_values_log.json
@@ -0,0 +1,113 @@
+{
+ "actions": [],
+ "creation": "2023-10-19 01:20:27.384150",
+ "default_view": "List",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "loan_collateral",
+ "trigger_doctype",
+ "trigger_document_docstatus",
+ "column_break_hime",
+ "posting_date",
+ "trigger_document",
+ "section_break_roay",
+ "previous_utilized_collateral_value",
+ "previous_available_collateral_value",
+ "column_break_nkjt",
+ "new_utilized_collateral_value",
+ "new_available_collateral_value"
+ ],
+ "fields": [
+ {
+ "fieldname": "loan_collateral",
+ "fieldtype": "Link",
+ "label": "Loan Collateral",
+ "options": "Loan Collateral"
+ },
+ {
+ "fieldname": "trigger_doctype",
+ "fieldtype": "Link",
+ "label": "Trigger DocType",
+ "options": "DocType"
+ },
+ {
+ "fieldname": "trigger_document",
+ "fieldtype": "Dynamic Link",
+ "label": "Trigger Document",
+ "options": "trigger_doctype"
+ },
+ {
+ "fieldname": "trigger_document_docstatus",
+ "fieldtype": "Int",
+ "label": "Trigger Document Docstatus"
+ },
+ {
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "label": "Posting Date"
+ },
+ {
+ "fieldname": "column_break_hime",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_roay",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_nkjt",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "previous_utilized_collateral_value",
+ "fieldtype": "Currency",
+ "label": "Previous Utilized Collateral Value",
+ "options": "Company:company:default_currency"
+ },
+ {
+ "fieldname": "previous_available_collateral_value",
+ "fieldtype": "Currency",
+ "label": "Previous Available Collateral Value",
+ "options": "Company:company:default_currency"
+ },
+ {
+ "fieldname": "new_utilized_collateral_value",
+ "fieldtype": "Currency",
+ "label": "New Utilized Collateral Value",
+ "options": "Company:company:default_currency"
+ },
+ {
+ "fieldname": "new_available_collateral_value",
+ "fieldtype": "Currency",
+ "label": "New Available Collateral Value",
+ "options": "Company:company:default_currency"
+ }
+ ],
+ "in_create": 1,
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2023-10-19 01:27:42.731347",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Loan Collateral Values Log",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/lending/loan_management/doctype/loan_collateral_values_log/loan_collateral_values_log.py b/lending/loan_management/doctype/loan_collateral_values_log/loan_collateral_values_log.py
new file mode 100644
index 00000000..b5cb7088
--- /dev/null
+++ b/lending/loan_management/doctype/loan_collateral_values_log/loan_collateral_values_log.py
@@ -0,0 +1,36 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe.model.document import Document
+from frappe.utils import nowdate
+
+
+class LoanCollateralValuesLog(Document):
+ pass
+
+
+def create_loan_collateral_values_log(
+ loan_collateral,
+ trigger_doctype,
+ trigger_document,
+ on_trigger_doc_cancel,
+ new_available_collateral_value,
+ new_utilized_collateral_value,
+ previous_available_collateral_value,
+ previous_utilized_collateral_value,
+):
+ doc = frappe.new_doc("Loan Collateral Values Log")
+
+ doc.loan_collateral = loan_collateral
+ doc.posting_date = nowdate()
+ doc.trigger_doctype = trigger_doctype
+ doc.trigger_document = trigger_document
+ doc.trigger_document_docstatus = 2 if on_trigger_doc_cancel else 1
+
+ doc.new_available_collateral_value = new_available_collateral_value
+ doc.new_utilized_collateral_value = new_utilized_collateral_value
+ doc.previous_available_collateral_value = previous_available_collateral_value
+ doc.previous_utilized_collateral_value = previous_utilized_collateral_value
+
+ doc.insert()
diff --git a/lending/loan_management/doctype/loan_collateral_values_log/test_loan_collateral_values_log.py b/lending/loan_management/doctype/loan_collateral_values_log/test_loan_collateral_values_log.py
new file mode 100644
index 00000000..4d41bd68
--- /dev/null
+++ b/lending/loan_management/doctype/loan_collateral_values_log/test_loan_collateral_values_log.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+from frappe.tests.utils import FrappeTestCase
+
+
+class TestLoanCollateralValuesLog(FrappeTestCase):
+ pass
diff --git a/lending/loan_management/doctype/loan_disbursement/loan_disbursement.py b/lending/loan_management/doctype/loan_disbursement/loan_disbursement.py
index bcf178d4..1a961dff 100644
--- a/lending/loan_management/doctype/loan_disbursement/loan_disbursement.py
+++ b/lending/loan_management/doctype/loan_disbursement/loan_disbursement.py
@@ -10,7 +10,11 @@
from erpnext.accounts.general_ledger import make_gl_entries
from erpnext.controllers.accounts_controller import AccountsController
-from lending.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import (
+from lending.loan_management.doctype.loan_collateral.loan_collateral import (
+ check_loan_collaterals_availability,
+ update_loan_collaterals_values,
+)
+from lending.loan_management.doctype.loan_collateral_deassignment.loan_collateral_deassignment import (
get_pledged_security_qty,
)
from lending.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import (
@@ -22,6 +26,7 @@ class LoanDisbursement(AccountsController):
def validate(self):
self.set_missing_values()
self.validate_disbursal_amount()
+ check_loan_collaterals_availability(self.against_loan, self.disbursed_amount)
def on_submit(self):
if self.is_term_loan:
@@ -29,8 +34,34 @@ def on_submit(self):
self.set_status_and_amounts()
self.withheld_security_deposit()
+
+ update_loan_collaterals_values(
+ self.against_loan, self.disbursed_amount, "Loan Disbursement", self.name, disbursement=True
+ )
+ self.set_status_of_loan_collaterals()
+
self.make_gl_entries()
+ def on_cancel(self):
+ if self.is_term_loan:
+ self.update_repayment_schedule_status(cancel=1)
+
+ self.delete_security_deposit()
+ self.set_status_and_amounts(cancel=1)
+
+ update_loan_collaterals_values(
+ self.against_loan,
+ self.disbursed_amount,
+ "Loan Disbursement",
+ self.name,
+ disbursement=True,
+ on_trigger_doc_cancel=1,
+ )
+ self.set_status_of_loan_collaterals(cancel=1)
+
+ self.make_gl_entries(cancel=1)
+ self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"]
+
def update_repayment_schedule_status(self, cancel=0):
if cancel:
status = "Initiated"
@@ -47,15 +78,6 @@ def update_repayment_schedule_status(self, cancel=0):
frappe.db.set_value("Loan Repayment Schedule", schedule, "status", status)
- def on_cancel(self):
- if self.is_term_loan:
- self.update_repayment_schedule_status(cancel=1)
-
- self.delete_security_deposit()
- self.set_status_and_amounts(cancel=1)
- self.make_gl_entries(cancel=1)
- self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"]
-
def set_missing_values(self):
if not self.disbursement_date:
self.disbursement_date = nowdate()
@@ -93,6 +115,29 @@ def validate_disbursal_amount(self):
elif self.disbursed_amount > possible_disbursal_amount:
frappe.throw(_("Disbursed Amount cannot be greater than {0}").format(possible_disbursal_amount))
+ def set_status_of_loan_collaterals(self, cancel=0):
+ if not frappe.db.get_value("Loan", self.against_loan, "is_secured_loan"):
+ return
+
+ if not cancel:
+ new_status = "Hypothecated"
+ old_status = "Pending Hypothecation"
+ else:
+ new_status = "Pending Hypothecation"
+ old_status = "Hypothecated"
+
+ frappe.db.sql(
+ """
+ UPDATE `tabLoan Collateral`
+ JOIN `tabLoan Collateral Assignment Loan Collateral` ON `tabLoan Collateral Assignment Loan Collateral`.`loan_collateral`=`tabLoan Collateral`.`name`
+ JOIN `tabLoan Collateral Assignment` ON `tabLoan Collateral Assignment`.`name`=`tabLoan Collateral Assignment Loan Collateral`.`parent`
+ JOIN `tabLoan` ON `tabLoan`.`name`=`tabLoan Collateral Assignment`.`loan`
+ SET `tabLoan Collateral`.`status`=%s
+ WHERE `tabLoan`.`name`=%s AND `tabLoan Collateral`.`status`=%s
+ """,
+ (new_status, self.against_loan, old_status),
+ )
+
def set_status_and_amounts(self, cancel=0):
loan_details = frappe.get_all(
"Loan",
@@ -373,4 +418,6 @@ def get_disbursal_amount(loan, on_current_security_price=0):
def get_maximum_amount_as_per_pledged_security(loan):
- return flt(frappe.db.get_value("Loan Security Pledge", {"loan": loan}, "sum(maximum_loan_value)"))
+ return flt(
+ frappe.db.get_value("Loan Collateral Assignment", {"loan": loan}, "sum(maximum_loan_value)")
+ )
diff --git a/lending/loan_management/doctype/loan_disbursement/test_loan_disbursement.py b/lending/loan_management/doctype/loan_disbursement/test_loan_disbursement.py
index b3698220..9852843a 100644
--- a/lending/loan_management/doctype/loan_disbursement/test_loan_disbursement.py
+++ b/lending/loan_management/doctype/loan_disbursement/test_loan_disbursement.py
@@ -21,9 +21,9 @@
create_demand_loan,
create_loan_accounts,
create_loan_application,
+ create_loan_collateral_assignment,
create_loan_product,
create_loan_security,
- create_loan_security_pledge,
create_loan_security_price,
create_loan_security_type,
create_repayment_entry,
@@ -154,7 +154,7 @@ def test_loan_topup_with_additional_pledge(self):
pledge1 = [{"loan_security": "Test Security 1", "qty": 2000.00}]
- create_loan_security_pledge(self.applicant, pledge1, loan=loan.name)
+ create_loan_collateral_assignment(self.applicant, pledge1, loan=loan.name)
# Topup 500000
make_loan_disbursement_entry(loan.name, 500000, disbursement_date=add_days(last_date, 1))
diff --git a/lending/loan_management/doctype/loan_repayment/loan_repayment.py b/lending/loan_management/doctype/loan_repayment/loan_repayment.py
index 9d9a7908..9e40d3c5 100644
--- a/lending/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/lending/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -11,6 +11,9 @@
from erpnext.controllers.accounts_controller import AccountsController
from lending.loan_management.doctype.loan.loan import update_all_linked_loan_customer_npa_status
+from lending.loan_management.doctype.loan_collateral.loan_collateral import (
+ update_loan_collaterals_values,
+)
from lending.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import (
get_last_accrual_date,
get_per_day_interest,
@@ -53,6 +56,10 @@ def on_submit(self):
if self.repayment_type == "Charges Waiver":
self.make_credit_note()
+ update_loan_collaterals_values(
+ self.against_loan, self.amount_paid, "Loan Repayment", self.name, repayment=True
+ )
+
self.make_gl_entries()
def on_cancel(self):
@@ -68,6 +75,15 @@ def on_cancel(self):
frappe.db.set_value("Loan", self.against_loan, "days_past_due", self.days_past_due)
+ update_loan_collaterals_values(
+ self.against_loan,
+ self.amount_paid,
+ "Loan Repayment",
+ self.name,
+ repayment=True,
+ on_trigger_doc_cancel=1,
+ )
+
self.ignore_linked_doctypes = [
"GL Entry",
"Payment Ledger Entry",
diff --git a/lending/loan_management/doctype/loan_security/loan_security_dashboard.py b/lending/loan_management/doctype/loan_security/loan_security_dashboard.py
index 1d96885e..adb84386 100644
--- a/lending/loan_management/doctype/loan_security/loan_security_dashboard.py
+++ b/lending/loan_management/doctype/loan_security/loan_security_dashboard.py
@@ -3,6 +3,6 @@ def get_data():
"fieldname": "loan_security",
"transactions": [
{"items": ["Loan Application", "Loan Security Price"]},
- {"items": ["Loan Security Pledge", "Loan Security Unpledge"]},
+ {"items": ["Loan Collateral Assignment", "Loan Collateral Deassignment"]},
],
}
diff --git a/lending/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py b/lending/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
index 4fa18136..225e83a4 100644
--- a/lending/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
+++ b/lending/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
@@ -6,7 +6,7 @@
from frappe.model.document import Document
from frappe.utils import flt, get_datetime
-from lending.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import (
+from lending.loan_management.doctype.loan_collateral_deassignment.loan_collateral_deassignment import (
get_pledged_security_qty,
)
@@ -51,13 +51,13 @@ def add_security(loan):
"Loan", loan, ["applicant", "company", "applicant_type"], as_dict=1
)
- loan_security_pledge = frappe.new_doc("Loan Security Pledge")
- loan_security_pledge.loan = loan
- loan_security_pledge.company = loan_details.company
- loan_security_pledge.applicant_type = loan_details.applicant_type
- loan_security_pledge.applicant = loan_details.applicant
+ loan_collateral_assignment = frappe.new_doc("Loan Collateral Assignment")
+ loan_collateral_assignment.loan = loan
+ loan_collateral_assignment.company = loan_details.company
+ loan_collateral_assignment.applicant_type = loan_details.applicant_type
+ loan_collateral_assignment.applicant = loan_details.applicant
- return loan_security_pledge.as_dict()
+ return loan_collateral_assignment.as_dict()
def check_for_ltv_shortfall(process_loan_security_shortfall):
diff --git a/lending/loan_management/doctype/loan_security_type/loan_security_type_dashboard.py b/lending/loan_management/doctype/loan_security_type/loan_security_type_dashboard.py
index 8fc4520c..6c56d242 100644
--- a/lending/loan_management/doctype/loan_security_type/loan_security_type_dashboard.py
+++ b/lending/loan_management/doctype/loan_security_type/loan_security_type_dashboard.py
@@ -3,6 +3,6 @@ def get_data():
"fieldname": "loan_security_type",
"transactions": [
{"items": ["Loan Security", "Loan Security Price"]},
- {"items": ["Loan Security Pledge", "Loan Security Unpledge"]},
+ {"items": ["Loan Collateral Assignment", "Loan Collateral Deassignment"]},
],
}
diff --git a/lending/loan_management/doctype/proposed_pledge/proposed_pledge.json b/lending/loan_management/doctype/proposed_pledge/proposed_pledge.json
index a0b3a79b..9c7e6668 100644
--- a/lending/loan_management/doctype/proposed_pledge/proposed_pledge.json
+++ b/lending/loan_management/doctype/proposed_pledge/proposed_pledge.json
@@ -37,7 +37,7 @@
"read_only": 1
},
{
- "fetch_from": "loan_security_pledge.qty",
+ "fetch_from": "loan_collateral_assignment.qty",
"fieldname": "qty",
"fieldtype": "Float",
"in_list_view": 1,
@@ -49,7 +49,8 @@
"fieldtype": "Link",
"in_list_view": 1,
"label": "Loan Security",
- "options": "Loan Security"
+ "options": "Loan Security",
+ "reqd": 1
},
{
"fieldname": "post_haircut_amount",
@@ -69,7 +70,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-01-17 07:29:01.671722",
+ "modified": "2023-10-19 04:47:47.266184",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Proposed Pledge",
@@ -78,5 +79,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/lending/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py b/lending/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py
index 02da8109..9f0001e2 100644
--- a/lending/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py
+++ b/lending/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py
@@ -190,7 +190,7 @@ def get_applicant_wise_total_loan_security_qty(filters, loan_security_details):
unpledges = frappe.db.sql(
"""
SELECT up.applicant, u.loan_security, sum(u.qty) as qty
- FROM `tabLoan Security Unpledge` up, `tabUnpledge` u
+ FROM `tabLoan Collateral Deassignment` up, `tabUnpledge` u
WHERE u.parent = up.name
AND up.status = 'Approved'
{conditions}
@@ -208,9 +208,9 @@ def get_applicant_wise_total_loan_security_qty(filters, loan_security_details):
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
+ FROM `tabLoan Collateral Assignment` lp, `tabPledge`p
WHERE p.parent = lp.name
- AND lp.status = 'Pledged'
+ AND lp.status = 'Assigned'
{conditions}
GROUP BY lp.applicant, p.loan_security
""".format(
diff --git a/lending/loan_management/report/loan_interest_report/loan_interest_report.py b/lending/loan_management/report/loan_interest_report/loan_interest_report.py
index 7614fb26..3cf82166 100644
--- a/lending/loan_management/report/loan_interest_report/loan_interest_report.py
+++ b/lending/loan_management/report/loan_interest_report/loan_interest_report.py
@@ -327,7 +327,7 @@ def get_loan_wise_pledges(filters):
unpledges = frappe.db.sql(
"""
SELECT up.loan, u.loan_security, sum(u.qty) as qty
- FROM `tabLoan Security Unpledge` up, `tabUnpledge` u
+ FROM `tabLoan Collateral Deassignment` up, `tabUnpledge` u
WHERE u.parent = up.name
AND up.status = 'Approved'
{conditions}
@@ -345,9 +345,9 @@ def get_loan_wise_pledges(filters):
pledges = frappe.db.sql(
"""
SELECT lp.loan, p.loan_security, sum(p.qty) as qty
- FROM `tabLoan Security Pledge` lp, `tabPledge`p
+ FROM `tabLoan Collateral Assignment` lp, `tabPledge`p
WHERE p.parent = lp.name
- AND lp.status = 'Pledged'
+ AND lp.status = 'Assigned'
{conditions}
GROUP BY lp.loan, p.loan_security
""".format(
diff --git a/lending/loan_management/report/loan_security_status/loan_security_status.js b/lending/loan_management/report/loan_security_status/loan_security_status.js
index 6e6191c7..29ac4e85 100644
--- a/lending/loan_management/report/loan_security_status/loan_security_status.js
+++ b/lending/loan_management/report/loan_security_status/loan_security_status.js
@@ -40,7 +40,7 @@ frappe.query_reports["Loan Security Status"] = {
"fieldname":"pledge_status",
"label": __("Pledge Status"),
"fieldtype": "Select",
- "options": ["", "Requested", "Pledged", "Partially Pledged", "Unpledged"],
+ "options": ["", "Requested", "Assigned", "Unassigned"],
},
]
};
diff --git a/lending/loan_management/report/loan_security_status/loan_security_status.py b/lending/loan_management/report/loan_security_status/loan_security_status.py
index 9a5a1800..f4ef6abf 100644
--- a/lending/loan_management/report/loan_security_status/loan_security_status.py
+++ b/lending/loan_management/report/loan_security_status/loan_security_status.py
@@ -15,10 +15,10 @@ def execute(filters=None):
def get_columns(filters):
columns = [
{
- "label": _("Loan Security Pledge"),
+ "label": _("Loan Collateral Assignment"),
"fieldtype": "Link",
- "fieldname": "loan_security_pledge",
- "options": "Loan Security Pledge",
+ "fieldname": "loan_collateral_assignment",
+ "options": "Loan Collateral Assignment",
"width": 200,
},
{"label": _("Loan"), "fieldtype": "Link", "fieldname": "loan", "options": "Loan", "width": 200},
@@ -65,13 +65,13 @@ def get_data(filters):
data = []
conditions = get_conditions(filters)
- loan_security_pledges = frappe.db.sql(
+ loan_collateral_assignments = frappe.db.sql(
"""
SELECT
p.name, p.applicant, p.loan, p.status, p.pledge_time,
c.loan_security, c.qty, c.loan_security_price, c.amount
FROM
- `tabLoan Security Pledge` p, `tabPledge` c
+ `tabLoan Collateral Assignment` p, `tabPledge` c
WHERE
p.docstatus = 1
AND c.parent = p.name
@@ -86,9 +86,9 @@ def get_data(filters):
default_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
- for pledge in loan_security_pledges:
+ for pledge in loan_collateral_assignments:
row = {}
- row["loan_security_pledge"] = pledge.name
+ row["loan_collateral_assignment"] = pledge.name
row["loan"] = pledge.loan
row["applicant"] = pledge.applicant
row["status"] = pledge.status
diff --git a/lending/loan_management/workspace/loan_management/loan_management.json b/lending/loan_management/workspace/loan_management/loan_management.json
index 6de28579..de46f981 100644
--- a/lending/loan_management/workspace/loan_management/loan_management.json
+++ b/lending/loan_management/workspace/loan_management/loan_management.json
@@ -178,9 +178,9 @@
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
- "label": "Loan Security Pledge",
+ "label": "Loan Collateral Assignment",
"link_count": 0,
- "link_to": "Loan Security Pledge",
+ "link_to": "Loan Collateral Assignment",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
@@ -189,9 +189,9 @@
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
- "label": "Loan Security Unpledge",
+ "label": "Loan Collateral Deassignment",
"link_count": 0,
- "link_to": "Loan Security Unpledge",
+ "link_to": "Loan Collateral Deassignment",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
diff --git a/lending/loan_management/workspace/loans/loans.json b/lending/loan_management/workspace/loans/loans.json
index ba5623b3..f6128dd7 100644
--- a/lending/loan_management/workspace/loans/loans.json
+++ b/lending/loan_management/workspace/loans/loans.json
@@ -139,9 +139,9 @@
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
- "label": "Loan Security Pledge",
+ "label": "Loan Collateral Assignment",
"link_count": 0,
- "link_to": "Loan Security Pledge",
+ "link_to": "Loan Collateral Assignment",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
@@ -150,9 +150,9 @@
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
- "label": "Loan Security Unpledge",
+ "label": "Loan Collateral Deassignment",
"link_count": 0,
- "link_to": "Loan Security Unpledge",
+ "link_to": "Loan Collateral Deassignment",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
diff --git a/lending/patches.txt b/lending/patches.txt
index e20037fa..6939fccf 100644
--- a/lending/patches.txt
+++ b/lending/patches.txt
@@ -5,6 +5,7 @@ lending.patches.v15_0.generate_loan_repayment_schedule
lending.patches.v15_0.rename_process_asset_classification_doctype
lending.patches.v15_0.rename_process_asset_classification_doctype_2
lending.patches.v15_0.rename_loan_type_to_loan_product
+lending.patches.v15_0.rename_loan_security_pledge_and_unpledge
[post_model_sync]
# Patches added in this section will be executed after doctypes are migrated
@@ -26,4 +27,6 @@ lending.patches.v15_0.rename_loan_partner_charge_type
lending.patches.v15_0.migrate_loan_type_to_loan_product
lending.patches.v15_0.add_loan_product_code_and_rename_loan_name
lending.patches.v15_0.update_penalty_interest_method_in_loan_products
-lending.patches.v15_0.update_min_bpi_application_days
\ No newline at end of file
+lending.patches.v15_0.update_min_bpi_application_days
+lending.patches.v15_0.set_collateral_type
+lending.patches.v15_0.change_status_of_loan_security_pledges
\ No newline at end of file
diff --git a/lending/patches/v15_0/change_status_of_loan_security_pledges.py b/lending/patches/v15_0/change_status_of_loan_security_pledges.py
new file mode 100644
index 00000000..6965978c
--- /dev/null
+++ b/lending/patches/v15_0/change_status_of_loan_security_pledges.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+
+import frappe
+
+
+def execute():
+ lca = frappe.qb.DocType("Loan Collateral Assignment")
+
+ (
+ frappe.qb.update(lca).set(
+ lca.status,
+ (
+ frappe.qb.terms.Case()
+ .when(lca.status == "Unpledged", "Unassigned")
+ .when(lca.status == "Pledged", "Assigned")
+ .else_(lca.status)
+ ),
+ )
+ ).run()
diff --git a/lending/patches/v15_0/rename_loan_security_pledge_and_unpledge.py b/lending/patches/v15_0/rename_loan_security_pledge_and_unpledge.py
new file mode 100644
index 00000000..0c6c2abb
--- /dev/null
+++ b/lending/patches/v15_0/rename_loan_security_pledge_and_unpledge.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+
+import frappe
+
+
+def execute():
+ if frappe.db.table_exists("Loan Security Pledge") and not frappe.db.table_exists(
+ "Loan Collateral Assignment"
+ ):
+ frappe.rename_doc("DocType", "Loan Security Pledge", "Loan Collateral Assignment", force=True)
+ frappe.reload_doc("loan_management", "doctype", "loan_collateral_assignment")
+
+ if frappe.db.table_exists("Loan Security Unpledge") and not frappe.db.table_exists(
+ "Loan Collateral Deassignment"
+ ):
+ frappe.rename_doc(
+ "DocType", "Loan Security Unpledge", "Loan Collateral Deassignment", force=True
+ )
+ frappe.reload_doc("loan_management", "doctype", "loan_collateral_deassignment")
diff --git a/lending/patches/v15_0/set_collateral_type.py b/lending/patches/v15_0/set_collateral_type.py
new file mode 100644
index 00000000..9715efcf
--- /dev/null
+++ b/lending/patches/v15_0/set_collateral_type.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+
+
+def execute():
+ lca = frappe.qb.DocType("Loan Collateral Assignment")
+ frappe.qb.update(lca).set(lca.collateral_type, "Loan Security").where(
+ lca.collateral_type.isnull()
+ ).run()
+
+ lcd = frappe.qb.DocType("Loan Collateral Deassignment")
+ frappe.qb.update(lcd).set(lcd.collateral_type, "Loan Security").where(
+ lcd.collateral_type.isnull()
+ ).run()
+
+ la = frappe.qb.DocType("Loan Application")
+ frappe.qb.update(la).set(la.collateral_type, "Loan Security").where(
+ la.is_secured_loan == 1
+ ).where(la.collateral_type.isnull()).run()
+
+ l = frappe.qb.DocType("Loan")
+ frappe.qb.update(l).set(l.collateral_type, "Loan Security").where(l.is_secured_loan == 1).where(
+ l.collateral_type.isnull()
+ ).run()