feat: item-wise negative stock setting (#29761)
This commit is contained in:
parent
749005eb8b
commit
eb8b424722
@ -172,9 +172,10 @@ class POSInvoice(SalesInvoice):
|
||||
frappe.throw(error_msg, title=_("Invalid Item"), as_list=True)
|
||||
|
||||
def validate_stock_availablility(self):
|
||||
from erpnext.stock.stock_ledger import is_negative_stock_allowed
|
||||
|
||||
if self.is_return or self.docstatus != 1:
|
||||
return
|
||||
allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')
|
||||
for d in self.get('items'):
|
||||
is_service_item = not (frappe.db.get_value('Item', d.get('item_code'), 'is_stock_item'))
|
||||
if is_service_item:
|
||||
@ -186,7 +187,7 @@ class POSInvoice(SalesInvoice):
|
||||
elif d.batch_no:
|
||||
self.validate_pos_reserved_batch_qty(d)
|
||||
else:
|
||||
if allow_negative_stock:
|
||||
if is_negative_stock_allowed(item_code=d.item_code):
|
||||
return
|
||||
|
||||
available_stock, is_stock_item = get_stock_availability(d.item_code, d.warehouse)
|
||||
|
@ -48,6 +48,7 @@
|
||||
"warranty_period",
|
||||
"weight_per_unit",
|
||||
"weight_uom",
|
||||
"allow_negative_stock",
|
||||
"reorder_section",
|
||||
"reorder_levels",
|
||||
"unit_of_measure_conversion",
|
||||
@ -907,6 +908,12 @@
|
||||
"fieldname": "is_grouped_asset",
|
||||
"fieldtype": "Check",
|
||||
"label": "Create Grouped Asset"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_negative_stock",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Negative Stock"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-tag",
|
||||
@ -914,7 +921,7 @@
|
||||
"image_field": "image",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2022-01-18 12:57:54.273202",
|
||||
"modified": "2022-02-11 08:07:46.663220",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item",
|
||||
|
@ -6,6 +6,7 @@ import json
|
||||
|
||||
import frappe
|
||||
from frappe.test_runner import make_test_objects
|
||||
from frappe.utils import add_days, today
|
||||
|
||||
from erpnext.controllers.item_variant import (
|
||||
InvalidItemAttributeValueError,
|
||||
@ -608,6 +609,45 @@ class TestItem(ERPNextTestCase):
|
||||
item.item_group = "All Item Groups"
|
||||
item.save() # if item code saved without item_code then series worked
|
||||
|
||||
@change_settings("Stock Settings", {"allow_negative_stock": 0})
|
||||
def test_item_wise_negative_stock(self):
|
||||
""" When global settings are disabled check that item that allows
|
||||
negative stock can still consume material in all known stock
|
||||
transactions that consume inventory."""
|
||||
from erpnext.stock.stock_ledger import is_negative_stock_allowed
|
||||
|
||||
item = make_item("_TestNegativeItemSetting", {"allow_negative_stock": 1, "valuation_rate": 100})
|
||||
self.assertTrue(is_negative_stock_allowed(item_code=item.name))
|
||||
|
||||
self.consume_item_code_with_differet_stock_transactions(item_code=item.name)
|
||||
|
||||
@change_settings("Stock Settings", {"allow_negative_stock": 0})
|
||||
def test_backdated_negative_stock(self):
|
||||
""" same as test above but backdated entries """
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
item = make_item("_TestNegativeItemSetting", {"allow_negative_stock": 1, "valuation_rate": 100})
|
||||
|
||||
# create a future entry so all new entries are backdated
|
||||
make_stock_entry(qty=1, item_code=item.name, target="_Test Warehouse - _TC", posting_date = add_days(today(), 5))
|
||||
self.consume_item_code_with_differet_stock_transactions(item_code=item.name)
|
||||
|
||||
|
||||
def consume_item_code_with_differet_stock_transactions(self, item_code, warehouse="_Test Warehouse - _TC"):
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
typical_args = {"item_code": item_code, "warehouse": warehouse}
|
||||
|
||||
create_delivery_note(**typical_args)
|
||||
create_sales_invoice(update_stock=1, **typical_args)
|
||||
make_stock_entry(item_code=item_code, source=warehouse, qty=1, purpose="Material Issue")
|
||||
make_stock_entry(item_code=item_code, source=warehouse, target="Stores - _TC", qty=1)
|
||||
# standalone return
|
||||
make_purchase_receipt(is_return=True, qty=-1, **typical_args)
|
||||
|
||||
|
||||
|
||||
def set_item_variant_settings(fields):
|
||||
doc = frappe.get_doc('Item Variant Settings')
|
||||
|
@ -433,9 +433,10 @@ class StockEntry(StockController):
|
||||
)
|
||||
|
||||
def set_actual_qty(self):
|
||||
allow_negative_stock = cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock"))
|
||||
from erpnext.stock.stock_ledger import is_negative_stock_allowed
|
||||
|
||||
for d in self.get('items'):
|
||||
allow_negative_stock = is_negative_stock_allowed(item_code=d.item_code)
|
||||
previous_sle = get_previous_sle({
|
||||
"item_code": d.item_code,
|
||||
"warehouse": d.s_warehouse or d.t_warehouse,
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
import copy
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
@ -268,11 +269,10 @@ class update_entries_after(object):
|
||||
self.verbose = verbose
|
||||
self.allow_zero_rate = allow_zero_rate
|
||||
self.via_landed_cost_voucher = via_landed_cost_voucher
|
||||
self.allow_negative_stock = allow_negative_stock \
|
||||
or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
|
||||
self.item_code = args.get("item_code")
|
||||
self.allow_negative_stock = allow_negative_stock or is_negative_stock_allowed(item_code=self.item_code)
|
||||
|
||||
self.args = frappe._dict(args)
|
||||
self.item_code = args.get("item_code")
|
||||
if self.args.sle_id:
|
||||
self.args['name'] = self.args.sle_id
|
||||
|
||||
@ -1049,10 +1049,7 @@ def get_datetime_limit_condition(detail):
|
||||
)"""
|
||||
|
||||
def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
|
||||
allow_negative_stock = cint(allow_negative_stock) \
|
||||
or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
|
||||
|
||||
if allow_negative_stock:
|
||||
if allow_negative_stock or is_negative_stock_allowed(item_code=args.item_code):
|
||||
return
|
||||
if not (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation"):
|
||||
return
|
||||
@ -1121,3 +1118,11 @@ def get_future_sle_with_negative_batch_qty(args):
|
||||
and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s)
|
||||
limit 1
|
||||
""", args, as_dict=1)
|
||||
|
||||
|
||||
def is_negative_stock_allowed(*, item_code: Optional[str] = None) -> bool:
|
||||
if cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock", cache=True)):
|
||||
return True
|
||||
if item_code and cint(frappe.db.get_value("Item", item_code, "allow_negative_stock", cache=True)):
|
||||
return True
|
||||
return False
|
||||
|
Loading…
x
Reference in New Issue
Block a user