Merge pull request #26081 from rohitwaghchaure/fixed-stock-entry-submission-performance
fix: time out while submit / cancel the stock transactions with more than 50 Items
diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py
index 1a6dbed..c6c6892 100644
--- a/erpnext/accounts/doctype/dunning/dunning.py
+++ b/erpnext/accounts/doctype/dunning/dunning.py
@@ -86,7 +86,7 @@
for reference in doc.references:
if reference.reference_doctype == 'Sales Invoice' and reference.outstanding_amount <= 0:
dunnings = frappe.get_list('Dunning', filters={
- 'sales_invoice': reference.reference_name, 'status': ('!=', 'Resolved')})
+ 'sales_invoice': reference.reference_name, 'status': ('!=', 'Resolved')}, ignore_permissions=True)
for dunning in dunnings:
frappe.db.set_value("Dunning", dunning.name, "status", 'Resolved')
@@ -96,7 +96,7 @@
grand_total = 0
if rate_of_interest:
interest_per_year = flt(outstanding_amount) * flt(rate_of_interest) / 100
- interest_amount = (interest_per_year * cint(overdue_days)) / 365
+ interest_amount = (interest_per_year * cint(overdue_days)) / 365
grand_total = flt(outstanding_amount) + flt(interest_amount) + flt(dunning_fee)
dunning_amount = flt(interest_amount) + flt(dunning_fee)
return {
diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.json b/erpnext/hr/notification/training_scheduled/training_scheduled.json
index e49541e..f365003 100644
--- a/erpnext/hr/notification/training_scheduled/training_scheduled.json
+++ b/erpnext/hr/notification/training_scheduled/training_scheduled.json
@@ -11,8 +11,8 @@
"event": "Submit",
"idx": 0,
"is_standard": 1,
- "message": "<table class=\"panel-header\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div class=\"text-medium text-muted\">\n <span>{{_(\"Training Event:\")}} {{ doc.event_name }}</span>\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>\n\n<table class=\"panel-body\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div>\n {{ doc.introduction }}\n <ul class=\"list-unstyled\" style=\"line-height: 1.7\">\n <li>{{_(\"Event Location\")}}: <b>{{ doc.location }}</b></li>\n {% set start = frappe.utils.get_datetime(doc.start_time) %}\n {% set end = frappe.utils.get_datetime(doc.end_time) %}\n {% if start.date() == end.date() %}\n <li>{{_(\"Date\")}}: <b>{{ start.strftime(\"%A, %d %b %Y\") }}</b></li>\n <li>\n {{_(\"Timing\")}}: <b>{{ start.strftime(\"%I:%M %p\") + ' to ' + end.strftime(\"%I:%M %p\") }}</b>\n </li>\n {% else %}\n <li>{{_(\"Start Time\")}}: <b>{{ start.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n </li>\n <li>{{_(\"End Time\")}}: <b>{{ end.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n </li>\n {% endif %}\n <li>{{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>\n {% if doc.is_mandatory %}\n <li>Note: This Training Event is mandatory</li>\n {% endif %}\n </ul>\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>",
- "modified": "2021-05-24 16:29:13.165930",
+ "message": "<table class=\"panel-header\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div class=\"text-medium text-muted\">\n <span>{{_(\"Training Event:\")}} {{ doc.event_name }}</span>\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>\n\n<table class=\"panel-body\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n <tr height=\"10\"></tr>\n <tr>\n <td width=\"15\"></td>\n <td>\n <div>\n {{ doc.introduction }}\n <ul class=\"list-unstyled\" style=\"line-height: 1.7\">\n <li>{{_(\"Event Location\")}}: <b>{{ doc.location }}</b></li>\n {% set start = frappe.utils.get_datetime(doc.start_time) %}\n {% set end = frappe.utils.get_datetime(doc.end_time) %}\n {% if start.date() == end.date() %}\n <li>{{_(\"Date\")}}: <b>{{ start.strftime(\"%A, %d %b %Y\") }}</b></li>\n <li>\n {{_(\"Timing\")}}: <b>{{ start.strftime(\"%I:%M %p\") + ' to ' + end.strftime(\"%I:%M %p\") }}</b>\n </li>\n {% else %}\n <li>\n {{_(\"Start Time\")}}: <b>{{ start.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b>\n </li>\n <li>{{_(\"End Time\")}}: <b>{{ end.strftime(\"%A, %d %b %Y at %I:%M %p\") }}</b></li>\n {% endif %}\n <li>{{ _(\"Event Link\") }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>\n {% if doc.is_mandatory %}\n <li>{{ _(\"Note: This Training Event is mandatory\") }}</li>\n {% endif %}\n </ul>\n </div>\n </td>\n <td width=\"15\"></td>\n </tr>\n <tr height=\"10\"></tr>\n</table>",
+ "modified": "2021-06-16 14:08:12.933367",
"modified_by": "Administrator",
"module": "HR",
"name": "Training Scheduled",
diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.md b/erpnext/hr/notification/training_scheduled/training_scheduled.md
index 418fd49..b9ba846 100644
--- a/erpnext/hr/notification/training_scheduled/training_scheduled.md
+++ b/erpnext/hr/notification/training_scheduled/training_scheduled.md
@@ -24,19 +24,19 @@
{% set start = frappe.utils.get_datetime(doc.start_time) %}
{% set end = frappe.utils.get_datetime(doc.end_time) %}
{% if start.date() == end.date() %}
- <li>{{_("Date")}}: <b>{{ start.strftime("%A, %d %b %Y") }}</b></li>
- <li>
- {{_("Timing")}}: <b>{{ start.strftime("%I:%M %p") + ' to ' + end.strftime("%I:%M %p") }}</b>
- </li>
+ <li>{{_("Date")}}: <b>{{ start.strftime("%A, %d %b %Y") }}</b></li>
+ <li>
+ {{_("Timing")}}: <b>{{ start.strftime("%I:%M %p") + ' to ' + end.strftime("%I:%M %p") }}</b>
+ </li>
{% else %}
- <li>{{_("Start Time")}}: <b>{{ start.strftime("%A, %d %b %Y at %I:%M %p") }}</b>
- </li>
- <li>{{_("End Time")}}: <b>{{ end.strftime("%A, %d %b %Y at %I:%M %p") }}</b>
- </li>
+ <li>
+ {{_("Start Time")}}: <b>{{ start.strftime("%A, %d %b %Y at %I:%M %p") }}</b>
+ </li>
+ <li>{{_("End Time")}}: <b>{{ end.strftime("%A, %d %b %Y at %I:%M %p") }}</b></li>
{% endif %}
- <li>{{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>
+ <li>{{ _("Event Link") }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}</li>
{% if doc.is_mandatory %}
- <li>Note: This Training Event is mandatory</li>
+ <li>{{ _("Note: This Training Event is mandatory") }}</li>
{% endif %}
</ul>
</div>
@@ -44,4 +44,4 @@
<td width="15"></td>
</tr>
<tr height="10"></tr>
-</table>
\ No newline at end of file
+</table>
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index 69d11a8..ff7fbbd 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -60,8 +60,9 @@
self.monthly_repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods)
def check_sanctioned_amount_limit(self):
- total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company)
sanctioned_amount_limit = get_sanctioned_amount_limit(self.applicant_type, self.applicant, self.company)
+ if sanctioned_amount_limit:
+ total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company)
if sanctioned_amount_limit and flt(self.loan_amount) + flt(total_loan_amount) > flt(sanctioned_amount_limit):
frappe.throw(_("Sanctioned Amount limit crossed for {0} {1}").format(self.applicant_type, frappe.bold(self.applicant)))
@@ -155,9 +156,29 @@
frappe.db.set_value("Loan", doc.name, "total_amount_paid", total_amount_paid)
def get_total_loan_amount(applicant_type, applicant, company):
- return frappe.db.get_value('Loan',
- {'applicant_type': applicant_type, 'company': company, 'applicant': applicant, 'docstatus': 1},
- 'sum(loan_amount)')
+ pending_amount = 0
+ loan_details = frappe.db.get_all("Loan",
+ filters={"applicant_type": applicant_type, "company": company, "applicant": applicant, "docstatus": 1,
+ "status": ("!=", "Closed")},
+ fields=["status", "total_payment", "disbursed_amount", "total_interest_payable", "total_principal_paid",
+ "written_off_amount"])
+
+ interest_amount = flt(frappe.db.get_value("Loan Interest Accrual", {"applicant_type": applicant_type,
+ "company": company, "applicant": applicant, "docstatus": 1}, "sum(interest_amount - paid_interest_amount)"))
+
+ for loan in loan_details:
+ if loan.status in ("Disbursed", "Loan Closure Requested"):
+ pending_amount += flt(loan.total_payment) - flt(loan.total_interest_payable) \
+ - flt(loan.total_principal_paid) - flt(loan.written_off_amount)
+ elif loan.status == "Partially Disbursed":
+ pending_amount += flt(loan.disbursed_amount) - flt(loan.total_interest_payable) \
+ - flt(loan.total_principal_paid) - flt(loan.written_off_amount)
+ elif loan.status == "Sanctioned":
+ pending_amount += flt(loan.total_payment)
+
+ pending_amount += interest_amount
+
+ return pending_amount
def get_sanctioned_amount_limit(applicant_type, applicant, company):
return frappe.db.get_value('Sanctioned Loan Amount',
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index fa4707c..314f58d 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -49,7 +49,11 @@
if not frappe.db.exists("Customer", "_Test Loan Customer"):
frappe.get_doc(get_customer_dict('_Test Loan Customer')).insert(ignore_permissions=True)
- self.applicant2 = frappe.db.get_value("Customer", {'name': '_Test Loan Customer'}, 'name')
+ if not frappe.db.exists("Customer", "_Test Loan Customer 1"):
+ frappe.get_doc(get_customer_dict("_Test Loan Customer 1")).insert(ignore_permissions=True)
+
+ self.applicant2 = frappe.db.get_value("Customer", {"name": "_Test Loan Customer"}, "name")
+ self.applicant3 = frappe.db.get_value("Customer", {"name": "_Test Loan Customer 1"}, "name")
create_loan(self.applicant1, "Personal Loan", 280000, "Repay Over Number of Periods", 20)
@@ -125,6 +129,38 @@
self.assertTrue(gl_entries1)
self.assertTrue(gl_entries2)
+ 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'")
+
+ if not frappe.db.get_value("Sanctioned Loan Amount", filters={"applicant_type": "Customer",
+ "applicant": "_Test Loan Customer 1", "company": "_Test Company"}):
+ frappe.get_doc({
+ "doctype": "Sanctioned Loan Amount",
+ "applicant_type": "Customer",
+ "applicant": "_Test Loan Customer 1",
+ "sanctioned_amount_limit": 1500000,
+ "company": "_Test Company"
+ }).insert(ignore_permissions=True)
+
+ # Make First Loan
+ pledge = [{
+ "loan_security": "Test Security 1",
+ "qty": 4000.00
+ }]
+
+ loan_application = create_loan_application('_Test Company', self.applicant3, 'Demand Loan', pledge)
+ create_pledge(loan_application)
+ loan = create_demand_loan(self.applicant3, "Demand Loan", loan_application, posting_date='2019-10-01')
+ loan.submit()
+
+ # Make second loan greater than the sanctioned amount
+ loan_application = create_loan_application('_Test Company', self.applicant3, 'Demand Loan', pledge,
+ do_not_save=True)
+ self.assertRaises(frappe.ValidationError, loan_application.save)
+
def test_regular_loan_repayment(self):
pledge = [{
"loan_security": "Test Security 1",
@@ -367,7 +403,7 @@
unpledge_request.load_from_db()
self.assertEqual(unpledge_request.docstatus, 1)
- def test_santined_loan_security_unpledge(self):
+ def test_sanctioned_loan_security_unpledge(self):
pledge = [{
"loan_security": "Test Security 1",
"qty": 4000.00
@@ -858,7 +894,7 @@
return lr
def create_loan_application(company, applicant, loan_type, proposed_pledges, repayment_method=None,
- repayment_periods=None, posting_date=None):
+ repayment_periods=None, posting_date=None, do_not_save=False):
loan_application = frappe.new_doc('Loan Application')
loan_application.applicant_type = 'Customer'
loan_application.company = company
@@ -874,6 +910,9 @@
for pledge in proposed_pledges:
loan_application.append('proposed_pledges', pledge)
+ if do_not_save:
+ return loan_application
+
loan_application.save()
loan_application.submit()
diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py
index 9c0147e..d8f3577 100644
--- a/erpnext/loan_management/doctype/loan_application/loan_application.py
+++ b/erpnext/loan_management/doctype/loan_application/loan_application.py
@@ -46,9 +46,11 @@
frappe.throw(_("Loan Amount exceeds maximum loan amount of {0} as per proposed securities").format(self.maximum_loan_amount))
def check_sanctioned_amount_limit(self):
- total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company)
sanctioned_amount_limit = get_sanctioned_amount_limit(self.applicant_type, self.applicant, self.company)
+ if sanctioned_amount_limit:
+ total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company)
+
if sanctioned_amount_limit and flt(self.loan_amount) + flt(total_loan_amount) > flt(sanctioned_amount_limit):
frappe.throw(_("Sanctioned Amount limit crossed for {0} {1}").format(self.applicant_type, frappe.bold(self.applicant)))
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 3d99b1f..b8b1a40 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -235,70 +235,71 @@
else:
remarks = _("Repayment against Loan: ") + self.against_loan
- if self.total_penalty_paid:
+ if not loan_details.repay_from_salary:
+ if self.total_penalty_paid:
+ gle_map.append(
+ self.get_gl_dict({
+ "account": loan_details.loan_account,
+ "against": loan_details.payment_account,
+ "debit": self.total_penalty_paid,
+ "debit_in_account_currency": self.total_penalty_paid,
+ "against_voucher_type": "Loan",
+ "against_voucher": self.against_loan,
+ "remarks": _("Penalty against loan:") + self.against_loan,
+ "cost_center": self.cost_center,
+ "party_type": self.applicant_type,
+ "party": self.applicant,
+ "posting_date": getdate(self.posting_date)
+ })
+ )
+
+ gle_map.append(
+ self.get_gl_dict({
+ "account": loan_details.penalty_income_account,
+ "against": loan_details.payment_account,
+ "credit": self.total_penalty_paid,
+ "credit_in_account_currency": self.total_penalty_paid,
+ "against_voucher_type": "Loan",
+ "against_voucher": self.against_loan,
+ "remarks": _("Penalty against loan:") + self.against_loan,
+ "cost_center": self.cost_center,
+ "posting_date": getdate(self.posting_date)
+ })
+ )
+
gle_map.append(
self.get_gl_dict({
- "account": loan_details.loan_account,
- "against": loan_details.payment_account,
- "debit": self.total_penalty_paid,
- "debit_in_account_currency": self.total_penalty_paid,
+ "account": loan_details.payment_account,
+ "against": loan_details.loan_account + ", " + loan_details.interest_income_account
+ + ", " + loan_details.penalty_income_account,
+ "debit": self.amount_paid,
+ "debit_in_account_currency": self.amount_paid,
"against_voucher_type": "Loan",
"against_voucher": self.against_loan,
- "remarks": _("Penalty against loan:") + self.against_loan,
+ "remarks": remarks,
"cost_center": self.cost_center,
- "party_type": self.applicant_type,
- "party": self.applicant,
"posting_date": getdate(self.posting_date)
})
)
gle_map.append(
self.get_gl_dict({
- "account": loan_details.penalty_income_account,
+ "account": loan_details.loan_account,
+ "party_type": loan_details.applicant_type,
+ "party": loan_details.applicant,
"against": loan_details.payment_account,
- "credit": self.total_penalty_paid,
- "credit_in_account_currency": self.total_penalty_paid,
+ "credit": self.amount_paid,
+ "credit_in_account_currency": self.amount_paid,
"against_voucher_type": "Loan",
"against_voucher": self.against_loan,
- "remarks": _("Penalty against loan:") + self.against_loan,
+ "remarks": remarks,
"cost_center": self.cost_center,
"posting_date": getdate(self.posting_date)
})
)
- gle_map.append(
- self.get_gl_dict({
- "account": loan_details.payment_account,
- "against": loan_details.loan_account + ", " + loan_details.interest_income_account
- + ", " + loan_details.penalty_income_account,
- "debit": self.amount_paid,
- "debit_in_account_currency": self.amount_paid,
- "against_voucher_type": "Loan",
- "against_voucher": self.against_loan,
- "remarks": remarks,
- "cost_center": self.cost_center,
- "posting_date": getdate(self.posting_date)
- })
- )
-
- gle_map.append(
- self.get_gl_dict({
- "account": loan_details.loan_account,
- "party_type": loan_details.applicant_type,
- "party": loan_details.applicant,
- "against": loan_details.payment_account,
- "credit": self.amount_paid,
- "credit_in_account_currency": self.amount_paid,
- "against_voucher_type": "Loan",
- "against_voucher": self.against_loan,
- "remarks": remarks,
- "cost_center": self.cost_center,
- "posting_date": getdate(self.posting_date)
- })
- )
-
- if gle_map:
- make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False)
+ if gle_map:
+ make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False)
def create_repayment_entry(loan, applicant, company, posting_date, loan_type,
payment_type, interest_payable, payable_principal_amount, amount_paid, penalty_amount=None):
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 306df99..2b51c1a 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -473,6 +473,13 @@
else:
self._submit()
+ def cancel(self):
+ if len(self.items) > 100:
+ msgprint(_("The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Submitted stage"))
+ self.queue_action('cancel', timeout=2000)
+ else:
+ self._cancel()
+
@frappe.whitelist()
def get_items(warehouse, posting_date, posting_time, company):
lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])
diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json
index bc29821..14712f8 100644
--- a/erpnext/support/doctype/issue/issue.json
+++ b/erpnext/support/doctype/issue/issue.json
@@ -166,7 +166,7 @@
"options": "Service Level Agreement"
},
{
- "depends_on": "eval: doc.status != 'Replied';",
+ "depends_on": "eval: doc.status != 'Replied' && doc.service_level_agreement;",
"fieldname": "response_by",
"fieldtype": "Datetime",
"label": "Response By",
@@ -180,7 +180,7 @@
"read_only": 1
},
{
- "depends_on": "eval: doc.status != 'Replied';",
+ "depends_on": "eval: doc.status != 'Replied' && doc.service_level_agreement;",
"fieldname": "resolution_by",
"fieldtype": "Datetime",
"label": "Resolution By",
@@ -410,7 +410,7 @@
"icon": "fa fa-ticket",
"idx": 7,
"links": [],
- "modified": "2021-05-26 10:49:07.574769",
+ "modified": "2021-06-10 03:22:27.098898",
"modified_by": "Administrator",
"module": "Support",
"name": "Issue",
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index b068363..9c69deb 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -29,6 +29,9 @@
self.update_status()
self.set_lead_contact(self.raised_by)
+ if not self.service_level_agreement:
+ self.reset_sla_fields()
+
def on_update(self):
# Add a communication in the issue timeline
if self.flags.create_communication and self.via_customer_portal:
@@ -54,6 +57,13 @@
self.company = frappe.db.get_value("Lead", self.lead, "company") or \
frappe.db.get_default("Company")
+ def reset_sla_fields(self):
+ self.agreement_status = ""
+ self.response_by = ""
+ self.resolution_by = ""
+ self.response_by_variance = 0
+ self.resolution_by_variance = 0
+
def update_status(self):
status = frappe.db.get_value("Issue", self.name, "status")
if self.status != "Open" and status == "Open" and not self.first_responded_on:
@@ -511,4 +521,4 @@
Converts datetime.time(10, 36, 55, 961454) to datetime.timedelta(seconds=38215)
"""
import datetime
- return datetime.timedelta(hours=time.hour, minutes=time.minute, seconds=time.second)
\ No newline at end of file
+ return datetime.timedelta(hours=time.hour, minutes=time.minute, seconds=time.second)