mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-12 17:23:38 +00:00
Merge pull request #52338 from rohitwaghchaure/fixed-negative-stock-error-for-purchase-return
fix: negative stock for purchase return
This commit is contained in:
@@ -5111,6 +5111,84 @@ class TestPurchaseReceipt(IntegrationTestCase):
|
||||
self.assertEqual(stk_ledger.incoming_rate, 120)
|
||||
self.assertEqual(stk_ledger.stock_value_difference, 600)
|
||||
|
||||
def test_negative_stock_error_for_purchase_return_when_stock_exists_in_future_date(self):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
from erpnext.stock.stock_ledger import NegativeStockError
|
||||
|
||||
item_code = make_item(
|
||||
"Test Negative Stock for Purchase Return with Future Stock Item",
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "TNSPFPRI.#####",
|
||||
},
|
||||
).name
|
||||
|
||||
make_purchase_receipt(
|
||||
item_code=item_code,
|
||||
posting_date=add_days(today(), -4),
|
||||
qty=100,
|
||||
rate=100,
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
)
|
||||
|
||||
pr1 = make_purchase_receipt(
|
||||
item_code=item_code,
|
||||
posting_date=add_days(today(), -3),
|
||||
qty=100,
|
||||
rate=100,
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
)
|
||||
|
||||
batch1 = get_batch_from_bundle(pr1.items[0].serial_and_batch_bundle)
|
||||
|
||||
pr2 = make_purchase_receipt(
|
||||
item_code=item_code,
|
||||
posting_date=add_days(today(), -2),
|
||||
qty=100,
|
||||
rate=100,
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
)
|
||||
|
||||
batch2 = get_batch_from_bundle(pr2.items[0].serial_and_batch_bundle)
|
||||
|
||||
make_stock_entry(
|
||||
item_code=item_code,
|
||||
qty=100,
|
||||
posting_date=add_days(today(), -1),
|
||||
source="_Test Warehouse - _TC",
|
||||
target="_Test Warehouse 1 - _TC",
|
||||
batch_no=batch1,
|
||||
use_serial_batch_fields=1,
|
||||
)
|
||||
|
||||
make_stock_entry(
|
||||
item_code=item_code,
|
||||
qty=100,
|
||||
posting_date=add_days(today(), -1),
|
||||
source="_Test Warehouse - _TC",
|
||||
target="_Test Warehouse 1 - _TC",
|
||||
batch_no=batch2,
|
||||
use_serial_batch_fields=1,
|
||||
)
|
||||
|
||||
make_stock_entry(
|
||||
item_code=item_code,
|
||||
qty=100,
|
||||
posting_date=today(),
|
||||
source="_Test Warehouse 1 - _TC",
|
||||
target="_Test Warehouse - _TC",
|
||||
batch_no=batch1,
|
||||
use_serial_batch_fields=1,
|
||||
)
|
||||
|
||||
make_purchase_entry = make_return_doc("Purchase Receipt", pr1.name)
|
||||
make_purchase_entry.set_posting_time = 1
|
||||
make_purchase_entry.posting_date = pr1.posting_date
|
||||
self.assertRaises(NegativeStockError, make_purchase_entry.submit)
|
||||
|
||||
|
||||
def prepare_data_for_internal_transfer():
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||
|
||||
@@ -17,6 +17,7 @@ from frappe.utils import (
|
||||
cint,
|
||||
cstr,
|
||||
flt,
|
||||
get_datetime,
|
||||
get_link_to_form,
|
||||
getdate,
|
||||
now,
|
||||
@@ -1451,31 +1452,44 @@ class SerialandBatchBundle(Document):
|
||||
for d in self.entries:
|
||||
available_qty = batch_wise_available_qty.get(d.batch_no, 0)
|
||||
if flt(available_qty, precision) < 0:
|
||||
frappe.throw(
|
||||
_(
|
||||
"""
|
||||
The Batch {0} of an item {1} has negative stock in the warehouse {2}. Please add a stock quantity of {3} to proceed with this entry."""
|
||||
).format(
|
||||
bold(d.batch_no),
|
||||
bold(self.item_code),
|
||||
bold(self.warehouse),
|
||||
bold(abs(flt(available_qty, precision))),
|
||||
),
|
||||
title=_("Negative Stock Error"),
|
||||
)
|
||||
self.throw_negative_batch(d.batch_no, available_qty, precision)
|
||||
|
||||
def throw_negative_batch(self, batch_no, available_qty, precision):
|
||||
from erpnext.stock.stock_ledger import NegativeStockError
|
||||
|
||||
frappe.throw(
|
||||
_(
|
||||
"""
|
||||
The Batch {0} of an item {1} has negative stock in the warehouse {2}. Please add a stock quantity of {3} to proceed with this entry."""
|
||||
).format(
|
||||
bold(batch_no),
|
||||
bold(self.item_code),
|
||||
bold(self.warehouse),
|
||||
bold(abs(flt(available_qty, precision))),
|
||||
),
|
||||
title=_("Negative Stock Error"),
|
||||
exc=NegativeStockError,
|
||||
)
|
||||
|
||||
def get_batchwise_available_qty(self):
|
||||
available_qty = self.get_available_qty_from_sabb()
|
||||
available_qty_from_ledger = self.get_available_qty_from_stock_ledger()
|
||||
batchwise_entries = self.get_available_qty_from_sabb()
|
||||
batchwise_entries.extend(self.get_available_qty_from_stock_ledger())
|
||||
|
||||
if not available_qty_from_ledger:
|
||||
return available_qty
|
||||
available_qty = frappe._dict({})
|
||||
batchwise_entries = sorted(
|
||||
batchwise_entries,
|
||||
key=lambda x: (get_datetime(x.get("posting_datetime")), get_datetime(x.get("creation"))),
|
||||
)
|
||||
|
||||
for batch_no, qty in available_qty_from_ledger.items():
|
||||
if batch_no in available_qty:
|
||||
available_qty[batch_no] += qty
|
||||
precision = frappe.get_precision("Serial and Batch Entry", "qty")
|
||||
for row in batchwise_entries:
|
||||
if row.batch_no in available_qty:
|
||||
available_qty[row.batch_no] += flt(row.qty)
|
||||
else:
|
||||
available_qty[batch_no] = qty
|
||||
available_qty[row.batch_no] = flt(row.qty)
|
||||
|
||||
if flt(available_qty[row.batch_no], precision) < 0:
|
||||
self.throw_negative_batch(row.batch_no, available_qty[row.batch_no], precision)
|
||||
|
||||
return available_qty
|
||||
|
||||
@@ -1488,7 +1502,9 @@ class SerialandBatchBundle(Document):
|
||||
frappe.qb.from_(sle)
|
||||
.select(
|
||||
sle.batch_no,
|
||||
Sum(sle.actual_qty).as_("available_qty"),
|
||||
sle.actual_qty.as_("qty"),
|
||||
sle.posting_datetime,
|
||||
sle.creation,
|
||||
)
|
||||
.where(
|
||||
(sle.item_code == self.item_code)
|
||||
@@ -1500,12 +1516,9 @@ class SerialandBatchBundle(Document):
|
||||
& (sle.batch_no.isnotnull())
|
||||
)
|
||||
.for_update()
|
||||
.groupby(sle.batch_no)
|
||||
)
|
||||
|
||||
res = query.run(as_list=True)
|
||||
|
||||
return frappe._dict(res) if res else frappe._dict()
|
||||
return query.run(as_dict=True)
|
||||
|
||||
def get_available_qty_from_sabb(self):
|
||||
batches = [d.batch_no for d in self.entries if d.batch_no]
|
||||
@@ -1516,7 +1529,9 @@ class SerialandBatchBundle(Document):
|
||||
frappe.qb.from_(child)
|
||||
.select(
|
||||
child.batch_no,
|
||||
Sum(child.qty).as_("available_qty"),
|
||||
child.qty,
|
||||
child.posting_datetime,
|
||||
child.creation,
|
||||
)
|
||||
.where(
|
||||
(child.item_code == self.item_code)
|
||||
@@ -1527,13 +1542,10 @@ class SerialandBatchBundle(Document):
|
||||
& (child.type_of_transaction.isin(["Inward", "Outward"]))
|
||||
)
|
||||
.for_update()
|
||||
.groupby(child.batch_no)
|
||||
)
|
||||
query = query.where(child.voucher_type != "Pick List")
|
||||
|
||||
res = query.run(as_list=True)
|
||||
|
||||
return frappe._dict(res) if res else frappe._dict()
|
||||
return query.run(as_dict=True)
|
||||
|
||||
def validate_voucher_no_docstatus(self):
|
||||
if self.voucher_type == "POS Invoice":
|
||||
|
||||
Reference in New Issue
Block a user