diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 15355753ff..7fed7367b1 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -197,9 +197,9 @@ class StockController(AccountsController): sl_dict.update(args) return sl_dict - def make_sl_entries(self, sl_entries, is_amended=None): + def make_sl_entries(self, sl_entries, is_amended=None, allow_negative_stock=False): from erpnext.stock.stock_ledger import make_sl_entries - make_sl_entries(sl_entries, is_amended) + make_sl_entries(sl_entries, is_amended, allow_negative_stock) def make_gl_entries_on_cancel(self): if frappe.db.sql("""select name from `tabGL Entry` where voucher_type=%s diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index e3269e87e2..1fb1e2d167 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -23,7 +23,7 @@ class Bin(Document): if (not getattr(self, f, None)) or (not self.get(f)): self.set(f, 0.0) - def update_stock(self, args): + def update_stock(self, args, allow_negative_stock=False): self.update_qty(args) if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation": @@ -38,7 +38,7 @@ class Bin(Document): "warehouse": self.warehouse, "posting_date": args.get("posting_date"), "posting_time": args.get("posting_time") - }) + }, allow_negative_stock=allow_negative_stock) def update_qty(self, args): # update the stock values (for current quantities) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 7bafcf6058..16f0f1c82e 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -93,13 +93,10 @@ class LandedCostVoucher(Document): # as those fields are allowed to edit after submit pr.save() - # delete stock ledger entries & gl entries for cancelled state of PR - - frappe.db.sql("""delete from `tabStock Ledger Entry` - where voucher_type='Purchase Receipt' and voucher_no=%s""", pr.name) - - frappe.db.sql("""delete from `tabGL Entry` - where voucher_type='Purchase Receipt' and voucher_no=%s""", pr.name) + # update stock & gl entries for cancelled state of PR + pr.docstatus = 2 + pr.update_stock_ledger(allow_negative_stock=True) + pr.make_gl_entries_on_cancel() # update stock & gl entries for submit state of PR pr.docstatus = 1 diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index f38ee5d5de..e04abbb2bc 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -129,7 +129,7 @@ class PurchaseReceipt(BuyingController): if not d.prevdoc_docname: frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code)) - def update_stock_ledger(self): + def update_stock_ledger(self, allow_negative_stock=False): sl_entries = [] stock_items = self.get_stock_items() @@ -153,7 +153,7 @@ class PurchaseReceipt(BuyingController): })) self.bk_flush_supp_wh(sl_entries) - self.make_sl_entries(sl_entries) + self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock) def update_ordered_qty(self): po_map = {} diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index eae1bf68bf..7bbf8fc3c5 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -14,7 +14,7 @@ class NegativeStockError(frappe.ValidationError): pass _exceptions = frappe.local('stockledger_exceptions') # _exceptions = [] -def make_sl_entries(sl_entries, is_amended=None): +def make_sl_entries(sl_entries, is_amended=None, allow_negative_stock=False): if sl_entries: from erpnext.stock.utils import update_bin @@ -35,7 +35,7 @@ def make_sl_entries(sl_entries, is_amended=None): "sle_id": sle_id, "is_amended": is_amended }) - update_bin(args) + update_bin(args, allow_negative_stock) if cancel: delete_cancelled_entry(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no')) @@ -58,7 +58,7 @@ def delete_cancelled_entry(voucher_type, voucher_no): frappe.db.sql("""delete from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) -def update_entries_after(args, allow_zero_rate=False, verbose=1): +def update_entries_after(args, allow_zero_rate=False, allow_negative_stock=False, verbose=1): """ update valution rate and qty after transaction from the current time-bucket onwards @@ -73,6 +73,9 @@ def update_entries_after(args, allow_zero_rate=False, verbose=1): if not _exceptions: frappe.local.stockledger_exceptions = [] + if not allow_negative_stock: + allow_negative_stock = cint(frappe.db.get_default("allow_negative_stock")) + previous_sle = get_sle_before_datetime(args) qty_after_transaction = flt(previous_sle.get("qty_after_transaction")) @@ -87,7 +90,7 @@ def update_entries_after(args, allow_zero_rate=False, verbose=1): stock_value_difference = 0.0 for sle in entries_to_fix: - if sle.serial_no or not cint(frappe.db.get_default("allow_negative_stock")): + if sle.serial_no or not allow_negative_stock: # validate negative stock for serialized items, fifo valuation # or when negative stock is not allowed for moving average if not validate_negative_stock(qty_after_transaction, sle): diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 889c30ced3..c08ed7da96 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -52,11 +52,11 @@ def get_bin(item_code, warehouse): bin_obj.ignore_permissions = True return bin_obj -def update_bin(args): +def update_bin(args, allow_negative_stock=False): is_stock_item = frappe.db.get_value('Item', args.get("item_code"), 'is_stock_item') if is_stock_item == 'Yes': bin = get_bin(args.get("item_code"), args.get("warehouse")) - bin.update_stock(args) + bin.update_stock(args, allow_negative_stock) return bin else: frappe.msgprint(_("Item {0} ignored since it is not a stock item").format(args.get("item_code")))