From a981b79865c6ccd0938692a6a133e00cd0bc52e7 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 14 Apr 2023 12:22:19 +0530 Subject: [PATCH 1/2] fix: too many writes error while making backdated stock reconciliation (cherry picked from commit 7bfc8f12367c14245682b32bb0853bbf044114c4) --- .../stock_reconciliation.py | 26 ++++++++++++++----- erpnext/stock/stock_ledger.py | 24 +++++++++++++---- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index e304bd181933..3fd4cec5d884 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -571,24 +571,33 @@ def cancel(self): self._cancel() def recalculate_current_qty(self, item_code, batch_no): + from erpnext.stock.stock_ledger import get_valuation_rate + + sl_entries = [] for row in self.items: if not (row.item_code == item_code and row.batch_no == batch_no): continue - row.current_qty = get_batch_qty_for_stock_reco( + current_qty = get_batch_qty_for_stock_reco( item_code, row.warehouse, batch_no, self.posting_date, self.posting_time, self.name ) - qty, val_rate = get_stock_balance( - item_code, - row.warehouse, - self.posting_date, - self.posting_time, - with_valuation_rate=True, + precesion = row.precision("current_qty") + if flt(current_qty, precesion) == flt(row.current_qty, precesion): + continue + + val_rate = get_valuation_rate( + item_code, row.warehouse, self.doctype, self.name, company=self.company, batch_no=batch_no ) row.current_valuation_rate = val_rate + if not row.current_qty and current_qty: + sle = self.get_sle_for_items(row) + sle.actual_qty = current_qty * -1 + sle.valuation_rate = val_rate + sl_entries.append(sle) + row.current_qty = current_qty row.db_set( { "current_qty": row.current_qty, @@ -597,6 +606,9 @@ def recalculate_current_qty(self, item_code, batch_no): } ) + if sl_entries: + self.make_sl_entries(sl_entries) + def get_batch_qty_for_stock_reco( item_code, warehouse, batch_no, posting_date, posting_time, voucher_no diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index b0a093def497..a605b0c24a9e 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -544,6 +544,14 @@ def process_sle(self, sle): if not self.args.get("sle_id"): self.get_dynamic_incoming_outgoing_rate(sle) + if ( + sle.voucher_type == "Stock Reconciliation" + and sle.batch_no + and sle.voucher_detail_no + and sle.actual_qty < 0 + ): + self.reset_actual_qty_for_stock_reco(sle) + if ( sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"] and sle.voucher_detail_no @@ -605,6 +613,16 @@ def process_sle(self, sle): if not self.args.get("sle_id"): self.update_outgoing_rate_on_transaction(sle) + def reset_actual_qty_for_stock_reco(self, sle): + current_qty = frappe.get_cached_value( + "Stock Reconciliation Item", sle.voucher_detail_no, "current_qty" + ) + + if current_qty: + sle.actual_qty = current_qty * -1 + elif current_qty == 0: + sle.is_cancelled = 1 + def validate_negative_stock(self, sle): """ validate negative stock for entries current datetime onwards @@ -1369,12 +1387,7 @@ def update_qty_in_future_sle(args, allow_negative_stock=False): def regenerate_sle_for_batch_stock_reco(detail): doc = frappe.get_cached_doc("Stock Reconciliation", detail.voucher_no) - doc.docstatus = 2 - doc.update_stock_ledger() - doc.recalculate_current_qty(detail.item_code, detail.batch_no) - doc.docstatus = 1 - doc.update_stock_ledger() doc.repost_future_sle_and_gle() @@ -1416,6 +1429,7 @@ def get_next_stock_reco(args): and voucher_type = 'Stock Reconciliation' and voucher_no != %(voucher_no)s and is_cancelled = 0 + and batch_no = %(batch_no)s and (timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s) or ( timestamp(posting_date, posting_time) = timestamp(%(posting_date)s, %(posting_time)s) From c53dc06f80cd86fcaa7c92dc8de42526f6b6c6c4 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 14 Apr 2023 13:00:12 +0530 Subject: [PATCH 2/2] fix: linters issues (cherry picked from commit d9dd64b4d2fcce9ffb6d2f21b5a9f56c39580b02) --- erpnext/controllers/stock_controller.py | 13 ++++-- erpnext/stock/stock_ledger.py | 61 +++++++++++++++---------- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 1e4fabe0d263..479fef72c669 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -859,6 +859,8 @@ def is_reposting_pending(): def future_sle_exists(args, sl_entries=None): key = (args.voucher_type, args.voucher_no) + if not hasattr(frappe.local, "future_sle"): + frappe.local.future_sle = {} if validate_future_sle_not_exists(args, key, sl_entries): return False @@ -892,6 +894,9 @@ def future_sle_exists(args, sl_entries=None): ) for d in data: + if key not in frappe.local.future_sle: + frappe.local.future_sle[key] = frappe._dict({}) + frappe.local.future_sle[key][(d.item_code, d.warehouse)] = d.total_row return len(data) @@ -903,6 +908,9 @@ def validate_future_sle_not_exists(args, key, sl_entries=None): item_key = (args.get("item_code"), args.get("warehouse")) if not sl_entries and hasattr(frappe.local, "future_sle"): + if key not in frappe.local.future_sle: + return False + if not frappe.local.future_sle.get(key) or ( item_key and item_key not in frappe.local.future_sle.get(key) ): @@ -910,11 +918,8 @@ def validate_future_sle_not_exists(args, key, sl_entries=None): 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({}) + return False if args.get("item_code"): item_key = (args.get("item_code"), args.get("warehouse")) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index a605b0c24a9e..03c04a54adc6 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1414,35 +1414,50 @@ def get_stock_reco_qty_shift(args): return stock_reco_qty_shift -def get_next_stock_reco(args): +def get_next_stock_reco(kwargs): """Returns next nearest stock reconciliaton's details.""" - return frappe.db.sql( - """ - select - name, posting_date, posting_time, creation, voucher_no, item_code, batch_no, actual_qty - from - `tabStock Ledger Entry` - where - item_code = %(item_code)s - and warehouse = %(warehouse)s - and voucher_type = 'Stock Reconciliation' - and voucher_no != %(voucher_no)s - and is_cancelled = 0 - and batch_no = %(batch_no)s - and (timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s) - or ( - timestamp(posting_date, posting_time) = timestamp(%(posting_date)s, %(posting_time)s) - and creation > %(creation)s + sle = frappe.qb.DocType("Stock Ledger Entry") + + query = ( + frappe.qb.from_(sle) + .select( + sle.name, + sle.posting_date, + sle.posting_time, + sle.creation, + sle.voucher_no, + sle.item_code, + sle.batch_no, + sle.actual_qty, + ) + .where( + (sle.item_code == kwargs.get("item_code")) + & (sle.warehouse == kwargs.get("warehouse")) + & (sle.voucher_type == "Stock Reconciliation") + & (sle.voucher_no != kwargs.get("voucher_no")) + & (sle.is_cancelled == 0) + & ( + ( + CombineDatetime(sle.posting_date, sle.posting_time) + > CombineDatetime(kwargs.get("posting_date"), kwargs.get("posting_time")) + | ( + ( + CombineDatetime(sle.posting_date, sle.posting_time) + == CombineDatetime(kwargs.get("posting_date"), kwargs.get("posting_time")) + ) + & (sle.creation > kwargs.get("creation")) + ) ) ) - order by timestamp(posting_date, posting_time) asc, creation asc - limit 1 - """, - args, - as_dict=1, + ) ) + if kwargs.get("batch_no"): + query.where(sle.batch_no == kwargs.get("batch_no")) + + return query.run(as_dict=True) + def get_datetime_limit_condition(detail): return f"""