Merge pull request #23211 from michellealva/tax-cat
feat: Allow Rename for Tax Category
diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py
index 2cb10b1..10b32fe 100644
--- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py
+++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py
@@ -173,7 +173,7 @@
from `tabGL Entry` gle
{join}
where
- gle.docstatus < 2 and gle.party_type=%(party_type)s and ifnull(gle.party, '') != ''
+ gle.docstatus < 2 and gle.is_cancelled = 0 and gle.party_type=%(party_type)s and ifnull(gle.party, '') != ''
and gle.posting_date <= %(to_date)s {conditions}
order by gle.posting_date
""".format(join=join, join_field=join_field, conditions=conditions), self.filters, as_dict=True)
@@ -248,7 +248,7 @@
from
`tabGL Entry`
where
- docstatus < 2
+ docstatus < 2 and is_cancelled = 0
and (voucher_type, voucher_no) in (
select voucher_type, voucher_no from `tabGL Entry` gle, `tabAccount` acc
where acc.name = gle.account and acc.account_type = '{income_or_expense}'
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index 23815d598..b75f7bd 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -17,6 +17,7 @@
from erpnext.loan_management.doctype.loan.loan import create_loan_security_unpledge
from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge
+from erpnext.loan_management.doctype.loan_disbursement.loan_disbursement import get_disbursal_amount
class TestLoan(unittest.TestCase):
def setUp(self):
@@ -323,6 +324,56 @@
self.assertEqual(loan.status, 'Closed')
self.assertEquals(sum(pledged_qty.values()), 0)
+ def test_disbursal_check_with_shortfall(self):
+ pledges = [{
+ "loan_security": "Test Security 2",
+ "qty": 8000.00,
+ "haircut": 50,
+ }]
+
+ loan_application = create_loan_application('_Test Company', self.applicant2,
+ 'Stock Loan', pledges, "Repay Over Number of Periods", 12)
+
+ create_pledge(loan_application)
+
+ loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application)
+ loan.submit()
+
+ #Disbursing 7,00,000 from the allowed 10,00,000 according to security pledge
+ make_loan_disbursement_entry(loan.name, 700000)
+
+ frappe.db.sql("""UPDATE `tabLoan Security Price` SET loan_security_price = 100
+ where loan_security='Test Security 2'""")
+
+ create_process_loan_security_shortfall()
+ loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name})
+ self.assertTrue(loan_security_shortfall)
+
+ self.assertEqual(get_disbursal_amount(loan.name), 0)
+
+ frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250
+ where loan_security='Test Security 2'""")
+
+ def test_disbursal_check_without_shortfall(self):
+ pledges = [{
+ "loan_security": "Test Security 2",
+ "qty": 8000.00,
+ "haircut": 50,
+ }]
+
+ loan_application = create_loan_application('_Test Company', self.applicant2,
+ 'Stock Loan', pledges, "Repay Over Number of Periods", 12)
+
+ create_pledge(loan_application)
+
+ loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application)
+ loan.submit()
+
+ #Disbursing 7,00,000 from the allowed 10,00,000 according to security pledge
+ make_loan_disbursement_entry(loan.name, 700000)
+
+ self.assertEqual(get_disbursal_amount(loan.name), 300000)
+
def create_loan_accounts():
if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"):
diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.js b/erpnext/loan_management/doctype/loan_application/loan_application.js
index b56fce1..1365274 100644
--- a/erpnext/loan_management/doctype/loan_application/loan_application.js
+++ b/erpnext/loan_management/doctype/loan_application/loan_application.js
@@ -33,18 +33,18 @@
if (frm.doc.is_secured_loan) {
frappe.db.get_value("Loan Security Pledge", {"loan_application": frm.doc.name, "docstatus": 1}, "name", (r) => {
- if (!r) {
+ if (Object.keys(r).length === 0) {
frm.add_custom_button(__('Loan Security Pledge'), function() {
- frm.trigger('create_loan_security_pledge')
+ frm.trigger('create_loan_security_pledge');
},__('Create'))
}
});
}
frappe.db.get_value("Loan", {"loan_application": frm.doc.name, "docstatus": 1}, "name", (r) => {
- if (!r) {
+ if (Object.keys(r).length === 0) {
frm.add_custom_button(__('Loan'), function() {
- frm.trigger('create_loan')
+ frm.trigger('create_loan');
},__('Create'))
} else {
frm.set_df_property('status', 'read_only', 1);
@@ -54,7 +54,7 @@
},
create_loan: function(frm) {
if (frm.doc.status != "Approved") {
- frappe.throw(__("Cannot create loan until application is approved"))
+ frappe.throw(__("Cannot create loan until application is approved"));
}
frappe.model.open_mapped_doc({
diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
index 6c27e12..260fada 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
+++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
@@ -67,28 +67,10 @@
disbursed_amount = self.disbursed_amount + loan_details.disbursed_amount
total_payment = loan_details.total_payment
- if disbursed_amount > loan_details.loan_amount and loan_details.is_term_loan:
- frappe.throw(_("Disbursed Amount cannot be greater than loan amount"))
+ possible_disbursal_amount = get_disbursal_amount(self.against_loan)
- if loan_details.status == 'Disbursed':
- pending_principal_amount = flt(loan_details.total_payment) - flt(loan_details.total_interest_payable) \
- - flt(loan_details.total_principal_paid)
- else:
- pending_principal_amount = loan_details.disbursed_amount
-
- security_value = 0.0
- if loan_details.is_secured_loan:
- security_value = get_total_pledged_security_value(self.against_loan)
-
- if not security_value:
- security_value = loan_details.loan_amount
-
- if pending_principal_amount + self.disbursed_amount > flt(security_value):
- allowed_amount = security_value - pending_principal_amount
- if allowed_amount < 0:
- allowed_amount = 0
-
- frappe.throw(_("Disbursed Amount cannot be greater than {0}").format(allowed_amount))
+ if self.disbursed_amount > possible_disbursal_amount:
+ frappe.throw(_("Disbursed Amount cannot be greater than {0}").format(possible_disbursal_amount))
if loan_details.status == "Disbursed" and not loan_details.is_term_loan:
process_loan_interest_accrual_for_demand_loans(posting_date=add_days(self.disbursement_date, -1),
@@ -176,3 +158,32 @@
security_value += (loan_security_price_map.get(security) * qty * hair_cut_map.get(security))/100
return security_value
+
+@frappe.whitelist()
+def get_disbursal_amount(loan):
+ loan_details = frappe.get_all("Loan", fields = ["loan_amount", "disbursed_amount", "total_payment",
+ "total_principal_paid", "total_interest_payable", "status", "is_term_loan", "is_secured_loan"],
+ filters= { "name": loan })[0]
+
+ if loan_details.is_secured_loan and frappe.get_all('Loan Security Shortfall', filters={'loan': loan,
+ 'status': 'Pending'}):
+ return 0
+
+ if loan_details.status == 'Disbursed':
+ pending_principal_amount = flt(loan_details.total_payment) - flt(loan_details.total_interest_payable) \
+ - flt(loan_details.total_principal_paid)
+ else:
+ pending_principal_amount = flt(loan_details.disbursed_amount)
+
+ security_value = 0.0
+ if loan_details.is_secured_loan:
+ security_value = get_total_pledged_security_value(loan)
+
+ if not security_value and not loan_details.is_secured_loan:
+ security_value = flt(loan_details.loan_amount)
+
+ disbursal_amount = flt(security_value) - flt(pending_principal_amount)
+
+ return disbursal_amount
+
+
diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
index c5111fd..1d3fa71 100644
--- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
+++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
@@ -85,8 +85,11 @@
if no_of_days <= 0:
return
- pending_principal_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
- - flt(loan.total_principal_paid)
+ if loan.status == 'Disbursed':
+ pending_principal_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
+ - flt(loan.total_principal_paid)
+ else:
+ pending_principal_amount = loan.disbursed_amount
interest_per_day = (pending_principal_amount * loan.rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100)
payable_interest = interest_per_day * no_of_days
@@ -107,7 +110,7 @@
def make_accrual_interest_entry_for_demand_loans(posting_date, process_loan_interest, open_loans=None, loan_type=None):
query_filters = {
- "status": "Disbursed",
+ "status": ('in', ['Disbursed', 'Partially Disbursed']),
"docstatus": 1
}
@@ -118,8 +121,9 @@
if not open_loans:
open_loans = frappe.get_all("Loan",
- fields=["name", "total_payment", "total_amount_paid", "loan_account", "interest_income_account", "is_term_loan",
- "disbursement_date", "applicant_type", "applicant", "rate_of_interest", "total_interest_payable", "repayment_start_date"],
+ fields=["name", "total_payment", "total_amount_paid", "loan_account", "interest_income_account",
+ "is_term_loan", "status", "disbursement_date", "disbursed_amount", "applicant_type", "applicant",
+ "rate_of_interest", "total_interest_payable", "total_principal_paid", "repayment_start_date"],
filters=query_filters)
for loan in open_loans:
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 9605045..451ae85 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -281,7 +281,7 @@
due_date = add_days(entry.posting_date, 1)
no_of_late_days = date_diff(posting_date,
- add_days(due_date, loan_type_details.grace_period_in_days))
+ add_days(due_date, loan_type_details.grace_period_in_days))
if no_of_late_days > 0 and (not against_loan_doc.repay_from_salary):
penalty_amount += (entry.interest_amount * (loan_type_details.penalty_interest_rate / 100) * no_of_late_days)/365
@@ -297,7 +297,10 @@
if not final_due_date:
final_due_date = add_days(due_date, loan_type_details.grace_period_in_days)
- pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid - against_loan_doc.total_interest_payable
+ if against_loan_doc.status == 'Disbursed':
+ pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid - against_loan_doc.total_interest_payable
+ else:
+ pending_principal_amount = against_loan_doc.disbursed_amount
if payment_type == "Loan Closure":
if due_date:
diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
index 02efe24..0f42bde 100644
--- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
+++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
-from frappe.utils import get_datetime
+from frappe.utils import get_datetime, flt
from frappe.model.document import Document
from six import iteritems
from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
@@ -51,13 +51,19 @@
"valid_upto": (">=", update_time)
}, as_list=1))
- loans = frappe.get_all('Loan', fields=['name', 'loan_amount', 'total_principal_paid'],
- filters={'status': 'Disbursed', 'is_secured_loan': 1})
+ loans = frappe.get_all('Loan', fields=['name', 'loan_amount', 'total_principal_paid', 'total_payment',
+ 'total_interest_payable', 'disbursed_amount', 'status'],
+ filters={'status': ('in',['Disbursed','Partially Disbursed']), 'is_secured_loan': 1})
loan_security_map = {}
for loan in loans:
- outstanding_amount = loan.loan_amount - loan.total_principal_paid
+ if loan.status == 'Disbursed':
+ outstanding_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
+ - flt(loan.total_principal_paid)
+ else:
+ outstanding_amount = loan.disbursed_amount
+
pledged_securities = get_pledged_security_qty(loan.name)
ltv_ratio = ''
security_value = 0.0
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
index add7bbf..cba6a2d 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
@@ -67,16 +67,16 @@
for key in scheduled_date:
description =frappe._("Reference: {0}, Item Code: {1} and Customer: {2}").format(self.name, d.item_code, self.customer)
- frappe.get_doc({
+ event = frappe.get_doc({
"doctype": "Event",
"owner": email_map.get(d.sales_person, self.owner),
"subject": description,
"description": description,
"starts_on": cstr(key["scheduled_date"]) + " 10:00:00",
"event_type": "Private",
- "ref_type": self.doctype,
- "ref_name": self.name
- }).insert(ignore_permissions=1)
+ })
+ event.add_participant(self.doctype, self.name)
+ event.insert(ignore_permissions=1)
frappe.db.set(self, 'status', 'Submitted')
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py
index d8ae17b..3c307e9 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py
@@ -2,6 +2,7 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
+from frappe.utils.data import get_datetime, add_days
import frappe
import unittest
@@ -9,4 +10,39 @@
# test_records = frappe.get_test_records('Maintenance Schedule')
class TestMaintenanceSchedule(unittest.TestCase):
- pass
+ def test_events_should_be_created_and_deleted(self):
+ ms = make_maintenance_schedule()
+ ms.generate_schedule()
+ ms.submit()
+
+ all_events = get_events(ms)
+ self.assertTrue(len(all_events) > 0)
+
+ ms.cancel()
+ events_after_cancel = get_events(ms)
+ self.assertTrue(len(events_after_cancel) == 0)
+
+def get_events(ms):
+ return frappe.get_all("Event Participants", filters={
+ "reference_doctype": ms.doctype,
+ "reference_docname": ms.name,
+ "parenttype": "Event"
+ })
+
+def make_maintenance_schedule():
+ ms = frappe.new_doc("Maintenance Schedule")
+ ms.company = "_Test Company"
+ ms.customer = "_Test Customer"
+ ms.transaction_date = get_datetime()
+
+ ms.append("items", {
+ "item_code": "_Test Item",
+ "start_date": get_datetime(),
+ "end_date": add_days(get_datetime(), 32),
+ "periodicity": "Weekly",
+ "no_of_visits": 4,
+ "sales_person": "Sales Team",
+ })
+ ms.insert(ignore_permissions=True)
+
+ return ms
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index ab095eb..20ae19f 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -285,9 +285,17 @@
return customer
else:
raise
- except frappe.MandatoryError:
+ except frappe.MandatoryError as e:
+ mandatory_fields = e.args[0].split(':')[1].split(',')
+ mandatory_fields = [customer.meta.get_label(field.strip()) for field in mandatory_fields]
+
frappe.local.message_log = []
- frappe.throw(_("Please create Customer from Lead {0}").format(lead_name))
+ lead_link = frappe.utils.get_link_to_form("Lead", lead_name)
+ message = _("Could not auto create Customer due to the following missing mandatory field(s):") + "<br>"
+ message += "<br><ul><li>" + "</li><li>".join(mandatory_fields) + "</li></ul>"
+ message += _("Please create Customer from Lead {0}.").format(lead_link)
+
+ frappe.throw(message, title=_("Mandatory Missing"))
else:
return customer_name
else: