Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: loan collateral (copy #106) #107

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions .github/helper/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/helper/site_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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(
Expand Down
45 changes: 28 additions & 17 deletions lending/loan_management/doctype/loan/loan.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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'));
}

Expand Down Expand Up @@ -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 : {
Expand All @@ -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");
}
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions lending/loan_management/doctype/loan/loan.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"loan_amount",
"rate_of_interest",
"is_secured_loan",
"collateral_type",
"disbursement_date",
"closure_date",
"disbursed_amount",
Expand Down Expand Up @@ -480,6 +481,7 @@
"label": "Loan Classification Details"
},
{
<<<<<<< HEAD
"fetch_from": "loan_type.loan_category",
"fieldname": "loan_category",
"fieldtype": "Link",
Expand All @@ -503,12 +505,24 @@
"fieldname": "loan_charges_section",
"fieldtype": "Section Break",
"label": "Loan Charges"
=======
"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"
>>>>>>> de301bc (feat: loan collateral)
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
<<<<<<< HEAD
"modified": "2023-10-13 19:15:47.245190",
=======
"modified": "2023-10-19 05:37:08.804432",
>>>>>>> de301bc (feat: loan collateral)
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan",
Expand Down
98 changes: 70 additions & 28 deletions lending/loan_management/doctype/loan/loan.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)

Expand Down Expand Up @@ -108,13 +111,13 @@ def set_default_charge_account(self):
charge.account = account

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"]

Expand Down Expand Up @@ -241,19 +244,19 @@ 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)",
)

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),
Expand All @@ -271,13 +274,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),
Expand Down Expand Up @@ -498,31 +503,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"]
)
# 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)

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 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:
Expand All @@ -544,16 +573,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

Expand Down
Loading
Loading