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
This commit is contained in:
rohitwaghchaure 2021-06-19 16:14:14 +05:30 committed by GitHub
commit a2bb1b8acd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 93 additions and 33 deletions

View File

@ -171,12 +171,13 @@ class BuyingController(StockController):
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 @@ class BuyingController(StockController):
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 @@ class BuyingController(StockController):
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'

View File

@ -501,7 +501,6 @@ class StockController(AccountsController):
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 @@ def make_quality_inspections(doctype, docname, items):
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 @@ def future_sle_exists(args):
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)

View File

@ -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 @@ class StockLedgerEntry(Document):
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):

View File

@ -22,6 +22,7 @@ _exceptions = frappe.local('stockledger_exceptions')
# _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 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc
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 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc
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 @@ class update_entries_after(object):
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()

View File

@ -177,7 +177,7 @@ def get_bin(item_code, warehouse):
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)