Merge pull request #26114 from rohitwaghchaure/fixed-stock-entry-submission-performance-v13-hotfix
fix: time out while submitting the stock transactions with more than 50 Items
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index da81911..20f5445 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -171,12 +171,13 @@
TODO: rename item_tax_amount to valuation_tax_amount
"""
+ stock_and_asset_items = []
stock_and_asset_items = self.get_stock_items() + self.get_asset_items()
stock_and_asset_items_qty, stock_and_asset_items_amount = 0, 0
last_item_idx = 1
for d in self.get("items"):
- if d.item_code and d.item_code in stock_and_asset_items:
+ if (d.item_code and d.item_code in stock_and_asset_items):
stock_and_asset_items_qty += flt(d.qty)
stock_and_asset_items_amount += flt(d.base_net_amount)
last_item_idx = d.idx
@@ -683,7 +684,8 @@
self.process_fixed_asset()
self.update_fixed_asset(field)
- update_last_purchase_rate(self, is_submit = 1)
+ if self.doctype in ['Purchase Order', 'Purchase Receipt']:
+ update_last_purchase_rate(self, is_submit = 1)
def on_cancel(self):
super(BuyingController, self).on_cancel()
@@ -691,7 +693,9 @@
if self.get('is_return'):
return
- update_last_purchase_rate(self, is_submit = 0)
+ if self.doctype in ['Purchase Order', 'Purchase Receipt']:
+ update_last_purchase_rate(self, is_submit = 0)
+
if self.doctype in ['Purchase Receipt', 'Purchase Invoice']:
field = 'purchase_invoice' if self.doctype == 'Purchase Invoice' else 'purchase_receipt'
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 6a7c9e3..35097b9 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -501,7 +501,6 @@
check_if_stock_and_account_balance_synced(self.posting_date,
self.company, self.doctype, self.name)
-
@frappe.whitelist()
def make_quality_inspections(doctype, docname, items):
if isinstance(items, str):
@@ -533,21 +532,75 @@
return inspections
-
def is_reposting_pending():
return frappe.db.exists("Repost Item Valuation",
{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
+def future_sle_exists(args, sl_entries=None):
+ key = (args.voucher_type, args.voucher_no)
-def future_sle_exists(args):
- sl_entries = frappe.get_all("Stock Ledger Entry",
+ if validate_future_sle_not_exists(args, key, sl_entries):
+ return False
+ elif get_cached_data(args, key):
+ return True
+
+ if not sl_entries:
+ sl_entries = get_sle_entries_against_voucher(args)
+ if not sl_entries:
+ return
+
+ or_conditions = get_conditions_to_validate_future_sle(sl_entries)
+
+ data = frappe.db.sql("""
+ select item_code, warehouse, count(name) as total_row
+ from `tabStock Ledger Entry`
+ where
+ ({})
+ and timestamp(posting_date, posting_time)
+ >= timestamp(%(posting_date)s, %(posting_time)s)
+ and voucher_no != %(voucher_no)s
+ and is_cancelled = 0
+ GROUP BY
+ item_code, warehouse
+ """.format(" or ".join(or_conditions)), args, as_dict=1)
+
+ for d in data:
+ frappe.local.future_sle[key][(d.item_code, d.warehouse)] = d.total_row
+
+ return len(data)
+
+def validate_future_sle_not_exists(args, key, sl_entries=None):
+ item_key = ''
+ if args.get('item_code'):
+ item_key = (args.get('item_code'), args.get('warehouse'))
+
+ if not sl_entries and hasattr(frappe.local, 'future_sle'):
+ if (not frappe.local.future_sle.get(key) or
+ (item_key and item_key not in frappe.local.future_sle.get(key))):
+ return True
+
+def get_cached_data(args, key):
+ if not hasattr(frappe.local, 'future_sle'):
+ frappe.local.future_sle = {}
+
+ if key not in frappe.local.future_sle:
+ frappe.local.future_sle[key] = frappe._dict({})
+
+ if args.get('item_code'):
+ item_key = (args.get('item_code'), args.get('warehouse'))
+ count = frappe.local.future_sle[key].get(item_key)
+
+ return True if (count or count == 0) else False
+ else:
+ return frappe.local.future_sle[key]
+
+def get_sle_entries_against_voucher(args):
+ return frappe.get_all("Stock Ledger Entry",
filters={"voucher_type": args.voucher_type, "voucher_no": args.voucher_no},
fields=["item_code", "warehouse"],
order_by="creation asc")
- if not sl_entries:
- return
-
+def get_conditions_to_validate_future_sle(sl_entries):
warehouse_items_map = {}
for entry in sl_entries:
if entry.warehouse not in warehouse_items_map:
@@ -561,17 +614,7 @@
f"""warehouse = {frappe.db.escape(warehouse)}
and item_code in ({', '.join(frappe.db.escape(item) for item in items)})""")
- return frappe.db.sql("""
- select name
- from `tabStock Ledger Entry`
- where
- ({})
- and timestamp(posting_date, posting_time)
- >= timestamp(%(posting_date)s, %(posting_time)s)
- and voucher_no != %(voucher_no)s
- and is_cancelled = 0
- limit 1
- """.format(" or ".join(or_conditions)), args)
+ return or_conditions
def create_repost_item_valuation_entry(args):
args = frappe._dict(args)
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index b0e7440..0febcb6 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -5,7 +5,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import flt, getdate, add_days, formatdate, get_datetime, date_diff
+from frappe.utils import flt, getdate, add_days, formatdate, get_datetime, cint
from frappe.model.document import Document
from datetime import date
from erpnext.controllers.item_variant import ItemTemplateCannotHaveStock
@@ -108,17 +108,18 @@
self.stock_uom = item_det.stock_uom
def check_stock_frozen_date(self):
- stock_frozen_upto = frappe.db.get_value('Stock Settings', None, 'stock_frozen_upto') or ''
- if stock_frozen_upto:
- stock_auth_role = frappe.db.get_value('Stock Settings', None,'stock_auth_role')
- if getdate(self.posting_date) <= getdate(stock_frozen_upto) and not stock_auth_role in frappe.get_roles():
- frappe.throw(_("Stock transactions before {0} are frozen").format(formatdate(stock_frozen_upto)), StockFreezeError)
+ stock_settings = frappe.get_doc('Stock Settings', 'Stock Settings')
- stock_frozen_upto_days = int(frappe.db.get_value('Stock Settings', None, 'stock_frozen_upto_days') or 0)
+ if stock_settings.stock_frozen_upto:
+ if (getdate(self.posting_date) <= getdate(stock_settings.stock_frozen_upto)
+ and stock_settings.stock_auth_role not in frappe.get_roles()):
+ frappe.throw(_("Stock transactions before {0} are frozen")
+ .format(formatdate(stock_settings.stock_frozen_upto)), StockFreezeError)
+
+ stock_frozen_upto_days = cint(stock_settings.stock_frozen_upto_days)
if stock_frozen_upto_days:
- stock_auth_role = frappe.db.get_value('Stock Settings', None,'stock_auth_role')
older_than_x_days_ago = (add_days(getdate(self.posting_date), stock_frozen_upto_days) <= date.today())
- if older_than_x_days_ago and not stock_auth_role in frappe.get_roles():
+ if older_than_x_days_ago and stock_settings.stock_auth_role not in frappe.get_roles():
frappe.throw(_("Not allowed to update stock transactions older than {0}").format(stock_frozen_upto_days), StockFreezeError)
def scrub_posting_time(self):
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index fc82c78..fb2ecab 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -22,6 +22,7 @@
# _exceptions = []
def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
+ from erpnext.controllers.stock_controller import future_sle_exists
if sl_entries:
from erpnext.stock.utils import update_bin
@@ -30,6 +31,9 @@
validate_cancellation(sl_entries)
set_as_cancel(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no'))
+ args = get_args_for_future_sle(sl_entries[0])
+ future_sle_exists(args, sl_entries)
+
for sle in sl_entries:
if sle.serial_no:
validate_serial_no(sle)
@@ -53,6 +57,14 @@
args = sle_doc.as_dict()
update_bin(args, allow_negative_stock, via_landed_cost_voucher)
+def get_args_for_future_sle(row):
+ return frappe._dict({
+ 'voucher_type': row.get('voucher_type'),
+ 'voucher_no': row.get('voucher_no'),
+ 'posting_date': row.get('posting_date'),
+ 'posting_time': row.get('posting_time')
+ })
+
def validate_serial_no(sle):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
for sn in get_serial_nos(sle.serial_no):
@@ -472,8 +484,8 @@
frappe.db.set_value("Purchase Receipt Item Supplied", sle.voucher_detail_no, "rate", outgoing_rate)
# Recalculate subcontracted item's rate in case of subcontracted purchase receipt/invoice
- if frappe.db.get_value(sle.voucher_type, sle.voucher_no, "is_subcontracted"):
- doc = frappe.get_doc(sle.voucher_type, sle.voucher_no)
+ if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_subcontracted") == 'Yes':
+ doc = frappe.get_cached_doc(sle.voucher_type, sle.voucher_no)
doc.update_valuation_rate(reset_outgoing_rate=False)
for d in (doc.items + doc.supplied_items):
d.db_update()
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 034d3eb..8a6a3a3 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -177,7 +177,7 @@
return bin_obj
def update_bin(args, allow_negative_stock=False, via_landed_cost_voucher=False):
- is_stock_item = frappe.db.get_value('Item', args.get("item_code"), 'is_stock_item')
+ is_stock_item = frappe.get_cached_value('Item', args.get("item_code"), 'is_stock_item')
if is_stock_item:
bin = get_bin(args.get("item_code"), args.get("warehouse"))
bin.update_stock(args, allow_negative_stock, via_landed_cost_voucher)