Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: Add indexes in stock queries and speed up bin updation #27758

Merged
merged 9 commits into from
Oct 12, 2021
2 changes: 1 addition & 1 deletion erpnext/controllers/stock_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ def future_sle_exists(args, sl_entries=None):

data = frappe.db.sql("""
select item_code, warehouse, count(name) as total_row
from `tabStock Ledger Entry`
from `tabStock Ledger Entry` force index (item_warehouse)
where
({})
and timestamp(posting_date, posting_time)
Expand Down
109 changes: 64 additions & 45 deletions erpnext/stock/doctype/bin/bin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,51 +14,6 @@ def before_save(self):
self.stock_uom = frappe.get_cached_value('Item', self.item_code, 'stock_uom')
self.set_projected_qty()

def update_stock(self, args, allow_negative_stock=False, via_landed_cost_voucher=False):
'''Called from erpnext.stock.utils.update_bin'''
self.update_qty(args)

if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation":
from erpnext.stock.stock_ledger import update_entries_after, update_qty_in_future_sle

if not args.get("posting_date"):
args["posting_date"] = nowdate()

if args.get("is_cancelled") and via_landed_cost_voucher:
return

# Reposts only current voucher SL Entries
# Updates valuation rate, stock value, stock queue for current transaction
update_entries_after({
"item_code": self.item_code,
"warehouse": self.warehouse,
"posting_date": args.get("posting_date"),
"posting_time": args.get("posting_time"),
"voucher_type": args.get("voucher_type"),
"voucher_no": args.get("voucher_no"),
"sle_id": args.name,
"creation": args.creation
}, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher)

# update qty in future ale and Validate negative qty
update_qty_in_future_sle(args, allow_negative_stock)


def update_qty(self, args):
# update the stock values (for current quantities)
if args.get("voucher_type")=="Stock Reconciliation":
self.actual_qty = args.get("qty_after_transaction")
else:
self.actual_qty = flt(self.actual_qty) + flt(args.get("actual_qty"))

self.ordered_qty = flt(self.ordered_qty) + flt(args.get("ordered_qty"))
self.reserved_qty = flt(self.reserved_qty) + flt(args.get("reserved_qty"))
self.indented_qty = flt(self.indented_qty) + flt(args.get("indented_qty"))
self.planned_qty = flt(self.planned_qty) + flt(args.get("planned_qty"))

self.set_projected_qty()
self.db_update()

def set_projected_qty(self):
self.projected_qty = (flt(self.actual_qty) + flt(self.ordered_qty)
+ flt(self.indented_qty) + flt(self.planned_qty) - flt(self.reserved_qty)
Expand Down Expand Up @@ -143,3 +98,67 @@ def update_reserved_qty_for_sub_contracting(self):

def on_doctype_update():
frappe.db.add_index("Bin", ["item_code", "warehouse"])


def update_stock(bin_name, args, allow_negative_stock=False, via_landed_cost_voucher=False):
'''Called from erpnext.stock.utils.update_bin'''
update_qty(bin_name, args)

if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation":
from erpnext.stock.stock_ledger import update_entries_after, update_qty_in_future_sle

if not args.get("posting_date"):
args["posting_date"] = nowdate()

if args.get("is_cancelled") and via_landed_cost_voucher:
return

# Reposts only current voucher SL Entries
# Updates valuation rate, stock value, stock queue for current transaction
update_entries_after({
"item_code": args.get('item_code'),
"warehouse": args.get('warehouse'),
"posting_date": args.get("posting_date"),
"posting_time": args.get("posting_time"),
"voucher_type": args.get("voucher_type"),
"voucher_no": args.get("voucher_no"),
"sle_id": args.get('name'),
"creation": args.get('creation')
}, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher)

# update qty in future sle and Validate negative qty
update_qty_in_future_sle(args, allow_negative_stock)

def get_bin_details(bin_name):
return frappe.db.get_value('Bin', bin_name, ['actual_qty', 'ordered_qty',
'reserved_qty', 'indented_qty', 'planned_qty', 'reserved_qty_for_production',
'reserved_qty_for_sub_contract'], as_dict=1)

def update_qty(bin_name, args):
bin_details = get_bin_details(bin_name)

# update the stock values (for current quantities)
if args.get("voucher_type")=="Stock Reconciliation":
actual_qty = args.get('qty_after_transaction')
else:
actual_qty = bin_details.actual_qty + flt(args.get("actual_qty"))

ordered_qty = flt(bin_details.ordered_qty) + flt(args.get("ordered_qty"))
reserved_qty = flt(bin_details.reserved_qty) + flt(args.get("reserved_qty"))
indented_qty = flt(bin_details.indented_qty) + flt(args.get("indented_qty"))
planned_qty = flt(bin_details.planned_qty) + flt(args.get("planned_qty"))


# compute projected qty
projected_qty = (flt(actual_qty) + flt(ordered_qty)
+ flt(indented_qty) + flt(planned_qty) - flt(reserved_qty)
- flt(bin_details.reserved_qty_for_production) - flt(bin_details.reserved_qty_for_sub_contract))

frappe.db.set_value('Bin', bin_name, {
'actual_qty': actual_qty,
'ordered_qty': ordered_qty,
'reserved_qty': reserved_qty,
'indented_qty': indented_qty,
'planned_qty': planned_qty,
'projected_qty': projected_qty
})
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-10-08 12:42:51.857631",
"modified": "2021-10-08 13:42:51.857631",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Ledger Entry",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,4 @@ def on_doctype_update():

frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"])
frappe.db.add_index("Stock Ledger Entry", ["batch_no", "item_code", "warehouse"])
frappe.db.add_index("Stock Ledger Entry", ["warehouse", "item_code"], "item_warehouse")
13 changes: 6 additions & 7 deletions erpnext/stock/stock_ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

import erpnext
from erpnext.stock.utils import (
get_bin,
get_incoming_outgoing_rate_for_cancel,
get_or_make_bin,
get_valuation_method,
)

Expand Down Expand Up @@ -805,14 +805,13 @@ def raise_exceptions(self):
def update_bin(self):
# update bin for each warehouse
for warehouse, data in iteritems(self.data):
bin_doc = get_bin(self.item_code, warehouse)
bin_doc.update({
bin_record = get_or_make_bin(self.item_code, warehouse)

frappe.db.set_value('Bin', bin_record, {
"valuation_rate": data.valuation_rate,
"actual_qty": data.qty_after_transaction,
"stock_value": data.stock_value
})
bin_doc.flags.via_stock_ledger_entry = True
bin_doc.save(ignore_permissions=True)


def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False):
Expand Down Expand Up @@ -918,7 +917,7 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
company = erpnext.get_default_company()

last_valuation_rate = frappe.db.sql("""select valuation_rate
from `tabStock Ledger Entry`
from `tabStock Ledger Entry` force index (item_warehouse)
where
item_code = %s
AND warehouse = %s
Expand All @@ -929,7 +928,7 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
if not last_valuation_rate:
# Get valuation rate from last sle for the item against any warehouse
last_valuation_rate = frappe.db.sql("""select valuation_rate
from `tabStock Ledger Entry`
from `tabStock Ledger Entry` force index (item_code)
where
item_code = %s
AND valuation_rate > 0
Expand Down
21 changes: 18 additions & 3 deletions erpnext/stock/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,27 @@ def get_bin(item_code, warehouse):
bin_obj.flags.ignore_permissions = True
return bin_obj

def get_or_make_bin(item_code, warehouse) -> str:
bin_record = frappe.db.get_value('Bin', {'item_code': item_code, 'warehouse': warehouse})

if not bin_record:
bin_obj = frappe.get_doc({
"doctype": "Bin",
"item_code": item_code,
"warehouse": warehouse,
})
bin_obj.flags.ignore_permissions = 1
bin_obj.insert()
bin_record = bin_obj.name

return bin_record

def update_bin(args, allow_negative_stock=False, via_landed_cost_voucher=False):
from erpnext.stock.doctype.bin.bin import update_stock
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)
return bin
bin_record = get_or_make_bin(args.get("item_code"), args.get("warehouse"))
update_stock(bin_record, args, allow_negative_stock, via_landed_cost_voucher)
else:
frappe.msgprint(_("Item {0} ignored since it is not a stock item").format(args.get("item_code")))

Expand Down