diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index da819119b10e..20f544572599 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -171,12 +171,13 @@ def update_valuation_rate(self, reset_outgoing_rate=True): 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 @@ def on_submit(self): 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 @@ def on_cancel(self): 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 6a7c9e3d0e77..35097b97b998 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -501,7 +501,6 @@ def repost_future_sle_and_gle(self): 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) + + 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 future_sle_exists(args): - sl_entries = frappe.get_all("Stock Ledger Entry", +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) 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 b0e7440e6cc0..0febcb68910d 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 @@ def validate_item(self): 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 fc82c789cc7c..fb2ecab24913 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -22,6 +22,7 @@ class SerialNoExistsInFutureTransaction(frappe.ValidationError): # _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 @@ def update_rate_on_purchase_receipt(self, sle, outgoing_rate): 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 034d3ebbb543..8a6a3a3e4a07 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -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)