Merge pull request #22142 from deepeshgarg007/loan_amount_precision

fix: Precision in loan amount calculation
This commit is contained in:
Deepesh Garg 2020-06-07 21:28:23 +05:30 committed by GitHub
commit 63e8391381
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 51 additions and 42 deletions

View File

@ -23,7 +23,7 @@
{ {
"hidden": 0, "hidden": 0,
"label": "Reports", "label": "Reports",
"links": "[\n {\n \"dependencies\": [\n \"Loan Repayment\"\n ],\n \"doctype\": \"Loan Repayment\",\n \"incomplete_dependencies\": [\n \"Loan Repayment\"\n ],\n \"is_query_report\": true,\n \"label\": \"Loan Repayment and Closure\",\n \"name\": \"Loan Repayment and Closure\",\n \"route\": \"#query-report/Loan Repayment and Closure\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Loan Security Pledge\"\n ],\n \"doctype\": \"Loan Security Pledge\",\n \"incomplete_dependencies\": [\n \"Loan Security Pledge\"\n ],\n \"is_query_report\": true,\n \"label\": \"Loan Security Status\",\n \"name\": \"Loan Security Status\",\n \"route\": \"#query-report/Loan Security Status\",\n \"type\": \"report\"\n }\n]" "links": "[\n {\n \"doctype\": \"Loan Repayment\",\n \"is_query_report\": true,\n \"label\": \"Loan Repayment and Closure\",\n \"name\": \"Loan Repayment and Closure\",\n \"route\": \"#query-report/Loan Repayment and Closure\",\n \"type\": \"report\"\n },\n {\n \"doctype\": \"Loan Security Pledge\",\n \"is_query_report\": true,\n \"label\": \"Loan Security Status\",\n \"name\": \"Loan Security Status\",\n \"route\": \"#query-report/Loan Security Status\",\n \"type\": \"report\"\n }\n]"
} }
], ],
"category": "Modules", "category": "Modules",
@ -34,11 +34,10 @@
"docstatus": 0, "docstatus": 0,
"doctype": "Desk Page", "doctype": "Desk Page",
"extends_another_page": 0, "extends_another_page": 0,
"hide_custom": 0,
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "Loan", "label": "Loan",
"modified": "2020-05-28 13:37:42.017709", "modified": "2020-06-07 19:42:14.947902",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan", "name": "Loan",

View File

@ -27,6 +27,7 @@ class LoanDisbursement(AccountsController):
def on_cancel(self): def on_cancel(self):
self.make_gl_entries(cancel=1) self.make_gl_entries(cancel=1)
self.ignore_linked_doctypes = ['GL Entry']
def set_missing_values(self): def set_missing_values(self):
if not self.disbursement_date: if not self.disbursement_date:

View File

@ -31,6 +31,7 @@ class LoanInterestAccrual(AccountsController):
self.update_is_accrued() self.update_is_accrued()
self.make_gl_entries(cancel=1) self.make_gl_entries(cancel=1)
self.ignore_linked_doctypes = ['GL Entry']
def update_is_accrued(self): def update_is_accrued(self):
frappe.db.set_value('Repayment Schedule', self.repayment_schedule_name, 'is_accrued', 0) frappe.db.set_value('Repayment Schedule', self.repayment_schedule_name, 'is_accrued', 0)
@ -176,14 +177,16 @@ def get_term_loans(date, term_loan=None, loan_type=None):
return term_loans return term_loans
def make_loan_interest_accrual_entry(args): def make_loan_interest_accrual_entry(args):
precision = cint(frappe.db.get_default("currency_precision")) or 2
loan_interest_accrual = frappe.new_doc("Loan Interest Accrual") loan_interest_accrual = frappe.new_doc("Loan Interest Accrual")
loan_interest_accrual.loan = args.loan loan_interest_accrual.loan = args.loan
loan_interest_accrual.applicant_type = args.applicant_type loan_interest_accrual.applicant_type = args.applicant_type
loan_interest_accrual.applicant = args.applicant loan_interest_accrual.applicant = args.applicant
loan_interest_accrual.interest_income_account = args.interest_income_account loan_interest_accrual.interest_income_account = args.interest_income_account
loan_interest_accrual.loan_account = args.loan_account loan_interest_accrual.loan_account = args.loan_account
loan_interest_accrual.pending_principal_amount = flt(args.pending_principal_amount, 2) loan_interest_accrual.pending_principal_amount = flt(args.pending_principal_amount, precision)
loan_interest_accrual.interest_amount = flt(args.interest_amount, 2) loan_interest_accrual.interest_amount = flt(args.interest_amount, precision)
loan_interest_accrual.posting_date = args.posting_date or nowdate() loan_interest_accrual.posting_date = args.posting_date or nowdate()
loan_interest_accrual.process_loan_interest_accrual = args.process_loan_interest loan_interest_accrual.process_loan_interest_accrual = args.process_loan_interest
loan_interest_accrual.repayment_schedule_name = args.repayment_schedule_name loan_interest_accrual.repayment_schedule_name = args.repayment_schedule_name

View File

@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
import json import json
from frappe import _ from frappe import _
from frappe.utils import flt, getdate from frappe.utils import flt, getdate, cint
from six import iteritems from six import iteritems
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import date_diff, add_days, getdate, add_months, get_first_day, get_datetime from frappe.utils import date_diff, add_days, getdate, add_months, get_first_day, get_datetime
@ -29,8 +29,11 @@ class LoanRepayment(AccountsController):
def on_cancel(self): def on_cancel(self):
self.mark_as_unpaid() self.mark_as_unpaid()
self.make_gl_entries(cancel=1) self.make_gl_entries(cancel=1)
self.ignore_linked_doctypes = ['GL Entry']
def set_missing_values(self, amounts): def set_missing_values(self, amounts):
precision = cint(frappe.db.get_default("currency_precision")) or 2
if not self.posting_date: if not self.posting_date:
self.posting_date = get_datetime() self.posting_date = get_datetime()
@ -38,24 +41,26 @@ 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 = flt(amounts['interest_amount'], 2) self.interest_payable = flt(amounts['interest_amount'], precision)
if not self.penalty_amount: if not self.penalty_amount:
self.penalty_amount = flt(amounts['penalty_amount'], 2) self.penalty_amount = flt(amounts['penalty_amount'], precision)
if not self.pending_principal_amount: if not self.pending_principal_amount:
self.pending_principal_amount = flt(amounts['pending_principal_amount'], 2) self.pending_principal_amount = flt(amounts['pending_principal_amount'], precision)
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 = flt(amounts['payable_principal_amount'], 2) self.payable_principal_amount = flt(amounts['payable_principal_amount'], precision)
if not self.payable_amount: if not self.payable_amount:
self.payable_amount = flt(amounts['payable_amount'], 2) self.payable_amount = flt(amounts['payable_amount'], precision)
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 validate_amount(self): def validate_amount(self):
precision = cint(frappe.db.get_default("currency_precision")) or 2
if not self.amount_paid: if not self.amount_paid:
frappe.throw(_("Amount paid cannot be zero")) frappe.throw(_("Amount paid cannot be zero"))
@ -63,11 +68,13 @@ class LoanRepayment(AccountsController):
msg = _("Paid amount cannot be less than {0}").format(self.penalty_amount) msg = _("Paid amount cannot be less than {0}").format(self.penalty_amount)
frappe.throw(msg) frappe.throw(msg)
if self.payment_type == "Loan Closure" and flt(self.amount_paid, 2) < flt(self.payable_amount, 2): if self.payment_type == "Loan Closure" and flt(self.amount_paid, precision) < flt(self.payable_amount, precision):
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): def update_paid_amount(self):
precision = cint(frappe.db.get_default("currency_precision")) or 2
loan = frappe.get_doc("Loan", self.against_loan) loan = frappe.get_doc("Loan", self.against_loan)
for payment in self.repayment_details: for payment in self.repayment_details:
@ -75,9 +82,9 @@ class LoanRepayment(AccountsController):
SET paid_principal_amount = `paid_principal_amount` + %s, SET paid_principal_amount = `paid_principal_amount` + %s,
paid_interest_amount = `paid_interest_amount` + %s paid_interest_amount = `paid_interest_amount` + %s
WHERE name = %s""", WHERE name = %s""",
(flt(payment.paid_principal_amount), flt(payment.paid_interest_amount), payment.loan_interest_accrual)) (flt(payment.paid_principal_amount, precision), flt(payment.paid_interest_amount, precision), payment.loan_interest_accrual))
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, precision) >= flt(loan.total_payment, precision):
if loan.is_secured_loan: if loan.is_secured_loan:
frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested") frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested")
else: else:
@ -253,6 +260,7 @@ def get_accrued_interest_entries(against_loan):
# 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
def get_amounts(amounts, against_loan, posting_date, payment_type): def get_amounts(amounts, against_loan, posting_date, payment_type):
precision = cint(frappe.db.get_default("currency_precision")) or 2
against_loan_doc = frappe.get_doc("Loan", against_loan) against_loan_doc = frappe.get_doc("Loan", against_loan)
loan_type_details = frappe.get_doc("Loan Type", against_loan_doc.loan_type) loan_type_details = frappe.get_doc("Loan Type", against_loan_doc.loan_type)
@ -282,8 +290,8 @@ def get_amounts(amounts, against_loan, posting_date, payment_type):
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, {
'interest_amount': flt(entry.interest_amount), 'interest_amount': flt(entry.interest_amount, precision),
'payable_principal_amount': flt(entry.payable_principal_amount) 'payable_principal_amount': flt(entry.payable_principal_amount, precision)
}) })
if not final_due_date: if not final_due_date:
@ -301,11 +309,11 @@ def get_amounts(amounts, against_loan, posting_date, payment_type):
per_day_interest = (payable_principal_amount * (loan_type_details.rate_of_interest / 100))/365 per_day_interest = (payable_principal_amount * (loan_type_details.rate_of_interest / 100))/365
total_pending_interest += (pending_days * per_day_interest) total_pending_interest += (pending_days * per_day_interest)
amounts["pending_principal_amount"] = pending_principal_amount amounts["pending_principal_amount"] = flt(pending_principal_amount, precision)
amounts["payable_principal_amount"] = payable_principal_amount amounts["payable_principal_amount"] = flt(payable_principal_amount, precision)
amounts["interest_amount"] = total_pending_interest amounts["interest_amount"] = flt(total_pending_interest, precision)
amounts["penalty_amount"] = penalty_amount amounts["penalty_amount"] = flt(penalty_amount, precision)
amounts["payable_amount"] = payable_principal_amount + total_pending_interest + penalty_amount amounts["payable_amount"] = flt(payable_principal_amount + total_pending_interest + penalty_amount, precision)
amounts["pending_accrual_entries"] = pending_accrual_entries amounts["pending_accrual_entries"] = pending_accrual_entries
if final_due_date: if final_due_date:

View File

@ -41,6 +41,7 @@
"options": "Company:company:default_currency" "options": "Company:company:default_currency"
}, },
{ {
"default": "0",
"fieldname": "rate_of_interest", "fieldname": "rate_of_interest",
"fieldtype": "Percent", "fieldtype": "Percent",
"label": "Rate of Interest (%) Yearly", "label": "Rate of Interest (%) Yearly",
@ -143,7 +144,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-04-15 00:24:43.259963", "modified": "2020-06-07 18:55:59.346292",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan Type", "name": "Loan Type",

View File

@ -76,7 +76,8 @@ def get_columns(filters):
"fieldtype": "Link", "fieldtype": "Link",
"fieldname": "currency", "fieldname": "currency",
"options": "Currency", "options": "Currency",
"width": 50 "width": 50,
"hidden": 1
} }
] ]
@ -84,17 +85,13 @@ def get_columns(filters):
def get_data(filters): def get_data(filters):
loan_security_price_map = frappe._dict(frappe.get_all("Loan Security",
fields=["name", "loan_security_price"], as_list=1
))
data = [] data = []
conditions = get_conditions(filters) conditions = get_conditions(filters)
loan_security_pledges = frappe.db.sql(""" loan_security_pledges = frappe.db.sql("""
SELECT SELECT
p.name, p.applicant, p.loan, p.status, p.pledge_time, p.name, p.applicant, p.loan, p.status, p.pledge_time,
c.loan_security, c.qty c.loan_security, c.qty, c.loan_security_price, c.amount
FROM FROM
`tabLoan Security Pledge` p, `tabPledge` c `tabLoan Security Pledge` p, `tabPledge` c
WHERE WHERE
@ -115,8 +112,8 @@ def get_data(filters):
row["pledge_time"] = pledge.pledge_time row["pledge_time"] = pledge.pledge_time
row["loan_security"] = pledge.loan_security row["loan_security"] = pledge.loan_security
row["qty"] = pledge.qty row["qty"] = pledge.qty
row["loan_security_price"] = loan_security_price_map.get(pledge.loan_security) row["loan_security_price"] = pledge.loan_security_price
row["loan_security_value"] = row["loan_security_price"] * pledge.qty row["loan_security_value"] = pledge.amount
row["currency"] = default_currency row["currency"] = default_currency
data.append(row) data.append(row)