Merge pull request #21305 from deepeshgarg007/partial_loan_repayment_fixes
fix: Loan Repayment code clean up and other fixes
This commit is contained in:
commit
3c6795a52d
@ -776,22 +776,16 @@ class SalarySlip(TransactionBase):
|
|||||||
|
|
||||||
for payment in self.get('loans'):
|
for payment in self.get('loans'):
|
||||||
amounts = calculate_amounts(payment.loan, self.posting_date, "Regular Payment")
|
amounts = calculate_amounts(payment.loan, self.posting_date, "Regular Payment")
|
||||||
|
total_amount = amounts['interest_amount'] + amounts['payable_principal_amount']
|
||||||
|
if payment.total_payment > total_amount:
|
||||||
|
frappe.throw(_("""Row {0}: Paid amount {1} is greater than pending accrued amount {2}
|
||||||
|
against loan {3}""").format(payment.idx, frappe.bold(payment.total_payment),
|
||||||
|
frappe.bold(total_amount), frappe.bold(payment.loan)))
|
||||||
|
|
||||||
if payment.interest_amount > amounts['interest_amount']:
|
|
||||||
frappe.throw(_("""Row {0}: Paid Interest amount {1} is greater than pending interest amount {2}
|
|
||||||
against loan {3}""").format(payment.idx, frappe.bold(payment.interest_amount),
|
|
||||||
frappe.bold(amounts['interest_amount']), frappe.bold(payment.loan)))
|
|
||||||
|
|
||||||
if payment.principal_amount > amounts['payable_principal_amount']:
|
|
||||||
frappe.throw(_("""Row {0}: Paid Principal amount {1} is greater than pending principal amount {2}
|
|
||||||
against loan {3}""").format(payment.idx, frappe.bold(payment.principal_amount),
|
|
||||||
frappe.bold(amounts['payable_principal_amount']), frappe.bold(payment.loan)))
|
|
||||||
|
|
||||||
payment.total_payment = payment.interest_amount + payment.principal_amount
|
|
||||||
self.total_interest_amount += payment.interest_amount
|
self.total_interest_amount += payment.interest_amount
|
||||||
self.total_principal_amount += payment.principal_amount
|
self.total_principal_amount += payment.principal_amount
|
||||||
|
|
||||||
self.total_loan_repayment = self.total_interest_amount + self.total_principal_amount
|
self.total_loan_repayment += payment.total_payment
|
||||||
|
|
||||||
def get_loan_details(self):
|
def get_loan_details(self):
|
||||||
|
|
||||||
|
@ -149,13 +149,19 @@ class TestLoan(unittest.TestCase):
|
|||||||
|
|
||||||
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 10), "Regular Payment", 111118.68)
|
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 10), "Regular Payment", 111118.68)
|
||||||
repayment_entry.save()
|
repayment_entry.save()
|
||||||
|
repayment_entry.submit()
|
||||||
|
|
||||||
penalty_amount = (accrued_interest_amount * 5 * 25) / (100 * days_in_year(get_datetime(first_date).year))
|
penalty_amount = (accrued_interest_amount * 5 * 25) / (100 * days_in_year(get_datetime(first_date).year))
|
||||||
|
|
||||||
self.assertEquals(flt(repayment_entry.interest_payable, 2), flt(accrued_interest_amount, 2))
|
|
||||||
self.assertEquals(flt(repayment_entry.penalty_amount, 2), flt(penalty_amount, 2))
|
self.assertEquals(flt(repayment_entry.penalty_amount, 2), flt(penalty_amount, 2))
|
||||||
|
|
||||||
repayment_entry.submit()
|
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
|
||||||
|
'paid_principal_amount'])
|
||||||
|
|
||||||
|
loan.load_from_db()
|
||||||
|
|
||||||
|
self.assertEquals(amounts[0], repayment_entry.interest_payable)
|
||||||
|
self.assertEquals(flt(loan.total_principal_paid, 2), flt(repayment_entry.amount_paid -
|
||||||
|
penalty_amount - amounts[0], 2))
|
||||||
|
|
||||||
def test_loan_closure_repayment(self):
|
def test_loan_closure_repayment(self):
|
||||||
pledges = []
|
pledges = []
|
||||||
@ -189,15 +195,19 @@ class TestLoan(unittest.TestCase):
|
|||||||
process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
|
process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
|
||||||
|
|
||||||
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
|
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
|
||||||
"Loan Closure", 13315.0681)
|
"Loan Closure", flt(loan.loan_amount + accrued_interest_amount))
|
||||||
repayment_entry.save()
|
repayment_entry.submit()
|
||||||
|
|
||||||
repayment_entry.amount_paid = repayment_entry.payable_amount
|
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
|
||||||
|
'paid_principal_amount'])
|
||||||
|
|
||||||
self.assertEquals(flt(repayment_entry.interest_payable, 3), flt(accrued_interest_amount, 3))
|
unaccrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * 6) \
|
||||||
|
/ (days_in_year(get_datetime(first_date).year) * 100)
|
||||||
|
|
||||||
|
self.assertEquals(flt(amounts[0] + unaccrued_interest_amount, 3),
|
||||||
|
flt(accrued_interest_amount, 3))
|
||||||
self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0)
|
self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0)
|
||||||
|
|
||||||
repayment_entry.submit()
|
|
||||||
loan.load_from_db()
|
loan.load_from_db()
|
||||||
self.assertEquals(loan.status, "Loan Closure Requested")
|
self.assertEquals(loan.status, "Loan Closure Requested")
|
||||||
|
|
||||||
@ -227,57 +237,15 @@ class TestLoan(unittest.TestCase):
|
|||||||
process_loan_interest_accrual_for_term_loans(posting_date=nowdate())
|
process_loan_interest_accrual_for_term_loans(posting_date=nowdate())
|
||||||
|
|
||||||
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(get_last_day(nowdate()), 5),
|
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(get_last_day(nowdate()), 5),
|
||||||
"Regular Payment", 89768.7534247)
|
"Regular Payment", 89768.75)
|
||||||
|
|
||||||
repayment_entry.save()
|
|
||||||
repayment_entry.submit()
|
repayment_entry.submit()
|
||||||
|
|
||||||
repayment_entry.load_from_db()
|
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
|
||||||
|
'paid_principal_amount'])
|
||||||
|
|
||||||
self.assertEquals(repayment_entry.interest_payable, 11250.00)
|
self.assertEquals(amounts[0], 11250.00)
|
||||||
self.assertEquals(repayment_entry.payable_principal_amount, 78303.00)
|
self.assertEquals(amounts[1], 78303.00)
|
||||||
|
|
||||||
def test_partial_loan_repayment(self):
|
|
||||||
pledges = []
|
|
||||||
pledges.append({
|
|
||||||
"loan_security": "Test Security 1",
|
|
||||||
"qty": 4000.00,
|
|
||||||
"haircut": 50
|
|
||||||
})
|
|
||||||
|
|
||||||
loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges)
|
|
||||||
|
|
||||||
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_security_pledge.name,
|
|
||||||
posting_date=get_first_day(nowdate()))
|
|
||||||
|
|
||||||
loan.submit()
|
|
||||||
|
|
||||||
self.assertEquals(loan.loan_amount, 1000000)
|
|
||||||
|
|
||||||
first_date = '2019-10-01'
|
|
||||||
last_date = '2019-10-30'
|
|
||||||
|
|
||||||
no_of_days = date_diff(last_date, first_date) + 1
|
|
||||||
|
|
||||||
accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
|
|
||||||
/ (days_in_year(get_datetime().year) * 100)
|
|
||||||
|
|
||||||
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
|
|
||||||
|
|
||||||
process_loan_interest_accrual_for_demand_loans(posting_date = add_days(first_date, 15))
|
|
||||||
process_loan_interest_accrual_for_demand_loans(posting_date = add_days(first_date, 30))
|
|
||||||
|
|
||||||
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 1), "Regular Payment", 6500)
|
|
||||||
repayment_entry.save()
|
|
||||||
repayment_entry.submit()
|
|
||||||
|
|
||||||
penalty_amount = (accrued_interest_amount * 4 * 25) / (100 * days_in_year(get_datetime(first_date).year))
|
|
||||||
|
|
||||||
lia1 = frappe.get_value("Loan Interest Accrual", {"loan": loan.name, "is_paid": 1}, 'name')
|
|
||||||
lia2 = frappe.get_value("Loan Interest Accrual", {"loan": loan.name, "is_paid": 0}, 'name')
|
|
||||||
|
|
||||||
self.assertTrue(lia1)
|
|
||||||
self.assertTrue(lia2)
|
|
||||||
|
|
||||||
def test_security_shortfall(self):
|
def test_security_shortfall(self):
|
||||||
pledges = []
|
pledges = []
|
||||||
@ -294,7 +262,7 @@ class TestLoan(unittest.TestCase):
|
|||||||
|
|
||||||
make_loan_disbursement_entry(loan.name, loan.loan_amount)
|
make_loan_disbursement_entry(loan.name, loan.loan_amount)
|
||||||
|
|
||||||
frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 100
|
frappe.db.sql("""UPDATE `tabLoan Security Price` SET loan_security_price = 100
|
||||||
where loan_security='Test Security 2'""")
|
where loan_security='Test Security 2'""")
|
||||||
|
|
||||||
create_process_loan_security_shortfall()
|
create_process_loan_security_shortfall()
|
||||||
|
@ -15,12 +15,13 @@
|
|||||||
"company",
|
"company",
|
||||||
"posting_date",
|
"posting_date",
|
||||||
"is_term_loan",
|
"is_term_loan",
|
||||||
"is_paid",
|
|
||||||
"section_break_7",
|
"section_break_7",
|
||||||
"pending_principal_amount",
|
"pending_principal_amount",
|
||||||
"payable_principal_amount",
|
"payable_principal_amount",
|
||||||
|
"paid_principal_amount",
|
||||||
"column_break_14",
|
"column_break_14",
|
||||||
"interest_amount",
|
"interest_amount",
|
||||||
|
"paid_interest_amount",
|
||||||
"section_break_15",
|
"section_break_15",
|
||||||
"process_loan_interest_accrual",
|
"process_loan_interest_accrual",
|
||||||
"repayment_schedule_name",
|
"repayment_schedule_name",
|
||||||
@ -101,13 +102,6 @@
|
|||||||
"label": "Company",
|
"label": "Company",
|
||||||
"options": "Company"
|
"options": "Company"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "is_paid",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Is Paid",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fetch_from": "loan.is_term_loan",
|
"fetch_from": "loan.is_term_loan",
|
||||||
@ -143,12 +137,24 @@
|
|||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Repayment Schedule Name",
|
"label": "Repayment Schedule Name",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "paid_principal_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Paid Principal Amount",
|
||||||
|
"options": "Company:company:default_currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "paid_interest_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Paid Interest Amount",
|
||||||
|
"options": "Company:company:default_currency"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-04-10 18:31:02.369857",
|
"modified": "2020-04-16 11:24:23.258404",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Loan Management",
|
"module": "Loan Management",
|
||||||
"name": "Loan Interest Accrual",
|
"name": "Loan Interest Accrual",
|
||||||
|
@ -30,9 +30,8 @@
|
|||||||
"reference_number",
|
"reference_number",
|
||||||
"column_break_21",
|
"column_break_21",
|
||||||
"reference_date",
|
"reference_date",
|
||||||
"paid_accrual_entries",
|
|
||||||
"partial_paid_entry",
|
|
||||||
"principal_amount_paid",
|
"principal_amount_paid",
|
||||||
|
"repayment_details",
|
||||||
"amended_from"
|
"amended_from"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@ -155,13 +154,6 @@
|
|||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "paid_accrual_entries",
|
|
||||||
"fieldtype": "Text",
|
|
||||||
"hidden": 1,
|
|
||||||
"label": "Paid Accrual Entries",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fetch_from": "against_loan.is_term_loan",
|
"fetch_from": "against_loan.is_term_loan",
|
||||||
@ -197,13 +189,6 @@
|
|||||||
"fieldname": "column_break_21",
|
"fieldname": "column_break_21",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "partial_paid_entry",
|
|
||||||
"fieldtype": "Text",
|
|
||||||
"hidden": 1,
|
|
||||||
"label": "Partial Paid Entry",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"default": "0.0",
|
"default": "0.0",
|
||||||
"fieldname": "principal_amount_paid",
|
"fieldname": "principal_amount_paid",
|
||||||
@ -225,11 +210,18 @@
|
|||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Due Date",
|
"label": "Due Date",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "repayment_details",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Repayment Details",
|
||||||
|
"options": "Loan Repayment Detail"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-02-26 06:18:54.934538",
|
"modified": "2020-04-16 18:14:45.166754",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Loan Management",
|
"module": "Loan Management",
|
||||||
"name": "Loan Repayment",
|
"name": "Loan Repayment",
|
||||||
@ -264,7 +256,6 @@
|
|||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
|
@ -19,11 +19,11 @@ class LoanRepayment(AccountsController):
|
|||||||
def validate(self):
|
def validate(self):
|
||||||
amounts = calculate_amounts(self.against_loan, self.posting_date, self.payment_type)
|
amounts = calculate_amounts(self.against_loan, self.posting_date, self.payment_type)
|
||||||
self.set_missing_values(amounts)
|
self.set_missing_values(amounts)
|
||||||
|
self.validate_amount()
|
||||||
def before_submit(self):
|
self.allocate_amounts(amounts['pending_accrual_entries'])
|
||||||
self.mark_as_paid()
|
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
|
self.update_paid_amount()
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
@ -38,32 +38,25 @@ class LoanRepayment(AccountsController):
|
|||||||
self.cost_center = erpnext.get_default_cost_center(self.company)
|
self.cost_center = erpnext.get_default_cost_center(self.company)
|
||||||
|
|
||||||
if not self.interest_payable:
|
if not self.interest_payable:
|
||||||
self.interest_payable = amounts['interest_amount']
|
self.interest_payable = flt(amounts['interest_amount'], 2)
|
||||||
|
|
||||||
if not self.penalty_amount:
|
if not self.penalty_amount:
|
||||||
self.penalty_amount = amounts['penalty_amount']
|
self.penalty_amount = flt(amounts['penalty_amount'], 2)
|
||||||
|
|
||||||
if not self.pending_principal_amount:
|
if not self.pending_principal_amount:
|
||||||
self.pending_principal_amount = amounts['pending_principal_amount']
|
self.pending_principal_amount = flt(amounts['pending_principal_amount'], 2)
|
||||||
|
|
||||||
if not self.payable_principal_amount and self.is_term_loan:
|
if not self.payable_principal_amount and self.is_term_loan:
|
||||||
self.payable_principal_amount = amounts['payable_principal_amount']
|
self.payable_principal_amount = flt(amounts['payable_principal_amount'], 2)
|
||||||
|
|
||||||
if not self.payable_amount:
|
if not self.payable_amount:
|
||||||
self.payable_amount = amounts['payable_amount']
|
self.payable_amount = flt(amounts['payable_amount'], 2)
|
||||||
|
|
||||||
if amounts.get('paid_accrual_entries'):
|
|
||||||
self.paid_accrual_entries = frappe.as_json(amounts.get('paid_accrual_entries'))
|
|
||||||
|
|
||||||
if amounts.get('due_date'):
|
if amounts.get('due_date'):
|
||||||
self.due_date = amounts.get('due_date')
|
self.due_date = amounts.get('due_date')
|
||||||
|
|
||||||
def mark_as_paid(self):
|
def validate_amount(self):
|
||||||
paid_entries = []
|
if not self.amount_paid:
|
||||||
paid_amount = self.amount_paid
|
|
||||||
interest_paid = paid_amount
|
|
||||||
|
|
||||||
if not paid_amount:
|
|
||||||
frappe.throw(_("Amount paid cannot be zero"))
|
frappe.throw(_("Amount paid cannot be zero"))
|
||||||
|
|
||||||
if self.amount_paid < self.penalty_amount:
|
if self.amount_paid < self.penalty_amount:
|
||||||
@ -74,37 +67,15 @@ class LoanRepayment(AccountsController):
|
|||||||
msg = _("Amount of {0} is required for Loan closure").format(self.payable_amount)
|
msg = _("Amount of {0} is required for Loan closure").format(self.payable_amount)
|
||||||
frappe.throw(msg)
|
frappe.throw(msg)
|
||||||
|
|
||||||
|
def update_paid_amount(self):
|
||||||
loan = frappe.get_doc("Loan", self.against_loan)
|
loan = frappe.get_doc("Loan", self.against_loan)
|
||||||
|
|
||||||
if self.paid_accrual_entries:
|
for payment in self.repayment_details:
|
||||||
paid_accrual_entries = json.loads(self.paid_accrual_entries)
|
frappe.db.sql(""" UPDATE `tabLoan Interest Accrual`
|
||||||
|
SET paid_principal_amount = `paid_principal_amount` + %s,
|
||||||
if paid_amount - self.penalty_amount > 0 and self.paid_accrual_entries:
|
paid_interest_amount = `paid_interest_amount` + %s
|
||||||
|
WHERE name = %s""",
|
||||||
interest_paid = paid_amount - self.penalty_amount
|
(flt(payment.paid_principal_amount), flt(payment.paid_interest_amount), payment.loan_interest_accrual))
|
||||||
|
|
||||||
for lia, interest_amount in iteritems(paid_accrual_entries):
|
|
||||||
if interest_amount <= interest_paid:
|
|
||||||
paid_entries.append(lia)
|
|
||||||
interest_paid -= interest_amount
|
|
||||||
elif interest_paid:
|
|
||||||
self.partial_paid_entry = frappe.as_json({"name": lia, "interest_amount": interest_amount})
|
|
||||||
frappe.db.set_value("Loan Interest Accrual", lia, "interest_amount",
|
|
||||||
interest_amount - interest_paid)
|
|
||||||
interest_paid = 0
|
|
||||||
|
|
||||||
if paid_entries:
|
|
||||||
self.paid_accrual_entries = frappe.as_json(paid_entries)
|
|
||||||
else:
|
|
||||||
self.paid_accrual_entries = ""
|
|
||||||
|
|
||||||
if interest_paid:
|
|
||||||
self.principal_amount_paid = interest_paid
|
|
||||||
|
|
||||||
if paid_entries:
|
|
||||||
frappe.db.sql("""UPDATE `tabLoan Interest Accrual`
|
|
||||||
SET is_paid = 1 where name in (%s)""" #nosec
|
|
||||||
% ", ".join(['%s']*len(paid_entries)), tuple(paid_entries))
|
|
||||||
|
|
||||||
if flt(loan.total_principal_paid + self.principal_amount_paid, 2) >= flt(loan.total_payment, 2):
|
if flt(loan.total_principal_paid + self.principal_amount_paid, 2) >= flt(loan.total_payment, 2):
|
||||||
frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested")
|
frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested")
|
||||||
@ -116,21 +87,14 @@ class LoanRepayment(AccountsController):
|
|||||||
update_shortfall_status(self.against_loan, self.principal_amount_paid)
|
update_shortfall_status(self.against_loan, self.principal_amount_paid)
|
||||||
|
|
||||||
def mark_as_unpaid(self):
|
def mark_as_unpaid(self):
|
||||||
|
|
||||||
loan = frappe.get_doc("Loan", self.against_loan)
|
loan = frappe.get_doc("Loan", self.against_loan)
|
||||||
|
|
||||||
if self.paid_accrual_entries:
|
for payment in self.repayment_details:
|
||||||
paid_accrual_entries = json.loads(self.paid_accrual_entries)
|
frappe.db.sql(""" UPDATE `tabLoan Interest Accrual`
|
||||||
|
SET paid_principal_amount = `paid_principal_amount` - %s,
|
||||||
if self.paid_accrual_entries:
|
paid_interest_amount = `paid_interest_amount` - %s
|
||||||
frappe.db.sql("""UPDATE `tabLoan Interest Accrual`
|
WHERE name = %s""",
|
||||||
SET is_paid = 0 where name in (%s)""" #nosec
|
(payment.paid_principal_amount, payment.paid_interest_amount, payment.loan_interest_accrual))
|
||||||
% ", ".join(['%s']*len(paid_accrual_entries)), tuple(paid_accrual_entries))
|
|
||||||
|
|
||||||
if self.partial_paid_entry:
|
|
||||||
partial_paid_entry = json.loads(self.partial_paid_entry)
|
|
||||||
frappe.db.set_value("Loan Interest Accrual", partial_paid_entry["name"], "interest_amount",
|
|
||||||
partial_paid_entry["interest_amount"])
|
|
||||||
|
|
||||||
frappe.db.sql(""" UPDATE `tabLoan` SET total_amount_paid = %s, total_principal_paid = %s
|
frappe.db.sql(""" UPDATE `tabLoan` SET total_amount_paid = %s, total_principal_paid = %s
|
||||||
WHERE name = %s """, (loan.total_amount_paid - self.amount_paid,
|
WHERE name = %s """, (loan.total_amount_paid - self.amount_paid,
|
||||||
@ -139,6 +103,38 @@ class LoanRepayment(AccountsController):
|
|||||||
if loan.status == "Loan Closure Requested":
|
if loan.status == "Loan Closure Requested":
|
||||||
frappe.db.set_value("Loan", self.against_loan, "status", "Disbursed")
|
frappe.db.set_value("Loan", self.against_loan, "status", "Disbursed")
|
||||||
|
|
||||||
|
def allocate_amounts(self, paid_entries):
|
||||||
|
self.set('repayment_details', [])
|
||||||
|
self.principal_amount_paid = 0
|
||||||
|
|
||||||
|
if self.amount_paid - self.penalty_amount > 0 and paid_entries:
|
||||||
|
interest_paid = self.amount_paid - self.penalty_amount
|
||||||
|
for lia, amounts in iteritems(paid_entries):
|
||||||
|
if amounts['interest_amount'] + amounts['payable_principal_amount'] <= interest_paid:
|
||||||
|
interest_amount = amounts['interest_amount']
|
||||||
|
paid_principal = amounts['payable_principal_amount']
|
||||||
|
self.principal_amount_paid += paid_principal
|
||||||
|
interest_paid -= (interest_amount + paid_principal)
|
||||||
|
elif interest_paid:
|
||||||
|
if interest_paid >= amounts['interest_amount']:
|
||||||
|
interest_amount = amounts['interest_amount']
|
||||||
|
paid_principal = interest_paid - interest_amount
|
||||||
|
self.principal_amount_paid += paid_principal
|
||||||
|
interest_paid = 0
|
||||||
|
else:
|
||||||
|
interest_amount = interest_paid
|
||||||
|
interest_paid = 0
|
||||||
|
paid_principal=0
|
||||||
|
|
||||||
|
self.append('repayment_details', {
|
||||||
|
'loan_interest_accrual': lia,
|
||||||
|
'paid_interest_amount': interest_amount,
|
||||||
|
'paid_principal_amount': paid_principal
|
||||||
|
})
|
||||||
|
|
||||||
|
if interest_paid:
|
||||||
|
self.principal_amount_paid += interest_paid
|
||||||
|
|
||||||
def make_gl_entries(self, cancel=0, adv_adj=0):
|
def make_gl_entries(self, cancel=0, adv_adj=0):
|
||||||
gle_map = []
|
gle_map = []
|
||||||
loan_details = frappe.get_doc("Loan", self.against_loan)
|
loan_details = frappe.get_doc("Loan", self.against_loan)
|
||||||
@ -223,7 +219,7 @@ def create_repayment_entry(loan, applicant, company, posting_date, loan_type,
|
|||||||
"posting_date": posting_date,
|
"posting_date": posting_date,
|
||||||
"applicant": applicant,
|
"applicant": applicant,
|
||||||
"penalty_amount": penalty_amount,
|
"penalty_amount": penalty_amount,
|
||||||
"interst_payable": interest_payable,
|
"interest_payable": interest_payable,
|
||||||
"payable_principal_amount": payable_principal_amount,
|
"payable_principal_amount": payable_principal_amount,
|
||||||
"amount_paid": amount_paid,
|
"amount_paid": amount_paid,
|
||||||
"loan_type": loan_type
|
"loan_type": loan_type
|
||||||
@ -232,15 +228,22 @@ def create_repayment_entry(loan, applicant, company, posting_date, loan_type,
|
|||||||
return lr
|
return lr
|
||||||
|
|
||||||
def get_accrued_interest_entries(against_loan):
|
def get_accrued_interest_entries(against_loan):
|
||||||
accrued_interest_entries = frappe.get_all("Loan Interest Accrual",
|
|
||||||
fields=["name", "interest_amount", "posting_date", "payable_principal_amount"],
|
|
||||||
filters = {
|
|
||||||
"loan": against_loan,
|
|
||||||
"is_paid": 0,
|
|
||||||
"docstatus": 1
|
|
||||||
}, order_by="posting_date")
|
|
||||||
|
|
||||||
return accrued_interest_entries
|
unpaid_accrued_entries = frappe.db.sql(
|
||||||
|
"""
|
||||||
|
SELECT name, posting_date, interest_amount - paid_interest_amount as interest_amount,
|
||||||
|
payable_principal_amount - paid_principal_amount as payable_principal_amount
|
||||||
|
FROM
|
||||||
|
`tabLoan Interest Accrual`
|
||||||
|
WHERE
|
||||||
|
loan = %s
|
||||||
|
AND (interest_amount - paid_interest_amount > 0 OR
|
||||||
|
payable_principal_amount - paid_principal_amount > 0)
|
||||||
|
AND
|
||||||
|
docstatus = 1
|
||||||
|
""", (against_loan), as_dict=1)
|
||||||
|
|
||||||
|
return unpaid_accrued_entries
|
||||||
|
|
||||||
# This function returns the amounts that are payable at the time of loan repayment based on posting date
|
# This function returns the amounts that are payable at the time of loan repayment based on posting date
|
||||||
# So it pulls all the unpaid Loan Interest Accrual Entries and calculates the penalty if applicable
|
# So it pulls all the unpaid Loan Interest Accrual Entries and calculates the penalty if applicable
|
||||||
@ -273,8 +276,10 @@ def get_amounts(amounts, against_loan, posting_date, payment_type):
|
|||||||
total_pending_interest += entry.interest_amount
|
total_pending_interest += entry.interest_amount
|
||||||
payable_principal_amount += entry.payable_principal_amount
|
payable_principal_amount += entry.payable_principal_amount
|
||||||
|
|
||||||
pending_accrual_entries.setdefault(entry.name,
|
pending_accrual_entries.setdefault(entry.name, {
|
||||||
flt(entry.interest_amount) + flt(entry.payable_principal_amount))
|
'interest_amount': flt(entry.interest_amount),
|
||||||
|
'payable_principal_amount': flt(entry.payable_principal_amount)
|
||||||
|
})
|
||||||
|
|
||||||
final_due_date = due_date
|
final_due_date = due_date
|
||||||
|
|
||||||
@ -291,7 +296,7 @@ def get_amounts(amounts, against_loan, posting_date, payment_type):
|
|||||||
amounts["interest_amount"] = total_pending_interest
|
amounts["interest_amount"] = total_pending_interest
|
||||||
amounts["penalty_amount"] = penalty_amount
|
amounts["penalty_amount"] = penalty_amount
|
||||||
amounts["payable_amount"] = payable_principal_amount + total_pending_interest + penalty_amount
|
amounts["payable_amount"] = payable_principal_amount + total_pending_interest + penalty_amount
|
||||||
amounts["paid_accrual_entries"] = pending_accrual_entries
|
amounts["pending_accrual_entries"] = pending_accrual_entries
|
||||||
|
|
||||||
if final_due_date:
|
if final_due_date:
|
||||||
amounts["due_date"] = final_due_date
|
amounts["due_date"] = final_due_date
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-04-15 18:31:54.026923",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"loan_interest_accrual",
|
||||||
|
"paid_principal_amount",
|
||||||
|
"paid_interest_amount"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "loan_interest_accrual",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Loan Interest Accrual",
|
||||||
|
"options": "Loan Interest Accrual"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "paid_principal_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Paid Principal Amount",
|
||||||
|
"options": "Company:company:default_currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "paid_interest_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Paid Interest Amount",
|
||||||
|
"options": "Company:company:default_currency"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-04-15 21:50:03.837019",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Loan Management",
|
||||||
|
"name": "Loan Repayment Detail",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class LoanRepaymentDetail(Document):
|
||||||
|
pass
|
@ -28,7 +28,6 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "loan_account",
|
"fieldname": "loan_account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Loan Account",
|
"label": "Loan Account",
|
||||||
"options": "Account",
|
"options": "Account",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
@ -50,21 +49,23 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Principal Amount",
|
"label": "Principal Amount",
|
||||||
"options": "Company:company:default_currency"
|
"options": "Company:company:default_currency",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "interest_amount",
|
"fieldname": "interest_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Interest Amount",
|
"label": "Interest Amount",
|
||||||
"options": "Company:company:default_currency"
|
"options": "Company:company:default_currency",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "total_payment",
|
"fieldname": "total_payment",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Total Payment",
|
"label": "Total Payment",
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency"
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "loan_repayment_entry",
|
"fieldname": "loan_repayment_entry",
|
||||||
@ -84,7 +85,7 @@
|
|||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-04-09 20:01:53.546364",
|
"modified": "2020-04-16 13:17:04.798335",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Loan Management",
|
"module": "Loan Management",
|
||||||
"name": "Salary Slip Loan",
|
"name": "Salary Slip Loan",
|
||||||
|
@ -287,7 +287,7 @@ erpnext.pos.PointOfSale = class PointOfSale {
|
|||||||
if (in_list(['serial_no', 'batch_no'], field)) {
|
if (in_list(['serial_no', 'batch_no'], field)) {
|
||||||
args[field] = value;
|
args[field] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add to cur_frm
|
// add to cur_frm
|
||||||
const item = this.frm.add_child('items', args);
|
const item = this.frm.add_child('items', args);
|
||||||
frappe.flags.hide_serial_batch_dialog = true;
|
frappe.flags.hide_serial_batch_dialog = true;
|
||||||
|
Loading…
Reference in New Issue
Block a user