mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-15 15:45:01 +00:00
fix: negative stock issue for higher precision
(cherry picked from commit87be020c78) # Conflicts: # erpnext/stock/doctype/delivery_note/test_delivery_note.py (cherry picked from commit1bbeecff12) # Conflicts: # erpnext/stock/doctype/delivery_note/test_delivery_note.py
This commit is contained in:
committed by
Mergify
parent
6267b9aea5
commit
21d13859a0
@@ -1507,6 +1507,639 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
|
||||
self.assertEqual(stock_value_difference, 100.0 * 5)
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
def test_delivery_note_return_valuation_without_use_serial_batch_field(self):
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return
|
||||
|
||||
batch_item = make_item(
|
||||
"_Test Delivery Note Return Valuation Batch Item",
|
||||
properties={
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"is_stock_item": 1,
|
||||
"batch_number_series": "BRTN-DNN-BI-.#####",
|
||||
},
|
||||
).name
|
||||
|
||||
serial_item = make_item(
|
||||
"_Test Delivery Note Return Valuation Serial Item",
|
||||
properties={"has_serial_no": 1, "is_stock_item": 1, "serial_no_series": "SRTN-DNN-TP-.#####"},
|
||||
).name
|
||||
|
||||
batches = {}
|
||||
serial_nos = []
|
||||
for qty, rate in {3: 300, 2: 100}.items():
|
||||
se = make_stock_entry(
|
||||
item_code=batch_item, target="_Test Warehouse - _TC", qty=qty, basic_rate=rate
|
||||
)
|
||||
batches[get_batch_from_bundle(se.items[0].serial_and_batch_bundle)] = qty
|
||||
|
||||
for qty, rate in {2: 100, 1: 50}.items():
|
||||
make_stock_entry(item_code=serial_item, target="_Test Warehouse - _TC", qty=qty, basic_rate=rate)
|
||||
serial_nos.extend(get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle))
|
||||
|
||||
dn = create_delivery_note(
|
||||
item_code=batch_item,
|
||||
qty=5,
|
||||
rate=1000,
|
||||
use_serial_batch_fields=0,
|
||||
batches=batches,
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
bundle_id = make_serial_batch_bundle(
|
||||
frappe._dict(
|
||||
{
|
||||
"item_code": serial_item,
|
||||
"warehouse": dn.items[0].warehouse,
|
||||
"qty": 3,
|
||||
"voucher_type": "Delivery Note",
|
||||
"serial_nos": serial_nos,
|
||||
"posting_date": dn.posting_date,
|
||||
"posting_time": dn.posting_time,
|
||||
"type_of_transaction": "Outward",
|
||||
"do_not_submit": True,
|
||||
}
|
||||
)
|
||||
).name
|
||||
|
||||
dn.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": serial_item,
|
||||
"qty": 3,
|
||||
"rate": 700,
|
||||
"base_rate": 700,
|
||||
"item_name": serial_item,
|
||||
"uom": "Nos",
|
||||
"stock_uom": "Nos",
|
||||
"conversion_factor": 1,
|
||||
"warehouse": dn.items[0].warehouse,
|
||||
"use_serial_batch_fields": 0,
|
||||
"serial_and_batch_bundle": bundle_id,
|
||||
},
|
||||
)
|
||||
|
||||
dn.save()
|
||||
dn.submit()
|
||||
dn.reload()
|
||||
|
||||
batch_no_valuation = defaultdict(float)
|
||||
serial_no_valuation = defaultdict(float)
|
||||
|
||||
for row in dn.items:
|
||||
if row.serial_and_batch_bundle:
|
||||
bundle_data = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
filters={"parent": row.serial_and_batch_bundle},
|
||||
fields=["incoming_rate", "serial_no", "batch_no"],
|
||||
)
|
||||
|
||||
for d in bundle_data:
|
||||
if d.batch_no:
|
||||
batch_no_valuation[d.batch_no] = d.incoming_rate
|
||||
elif d.serial_no:
|
||||
serial_no_valuation[d.serial_no] = d.incoming_rate
|
||||
|
||||
return_entry = make_sales_return(dn.name)
|
||||
|
||||
return_entry.save()
|
||||
return_entry.submit()
|
||||
return_entry.reload()
|
||||
|
||||
for row in return_entry.items:
|
||||
if row.item_code == batch_item:
|
||||
bundle_data = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
filters={"parent": row.serial_and_batch_bundle},
|
||||
fields=["incoming_rate", "batch_no"],
|
||||
)
|
||||
|
||||
for d in bundle_data:
|
||||
self.assertEqual(d.incoming_rate, batch_no_valuation[d.batch_no])
|
||||
else:
|
||||
bundle_data = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
filters={"parent": row.serial_and_batch_bundle},
|
||||
fields=["incoming_rate", "serial_no"],
|
||||
)
|
||||
|
||||
for d in bundle_data:
|
||||
self.assertEqual(d.incoming_rate, serial_no_valuation[d.serial_no])
|
||||
|
||||
def test_delivery_note_return_valuation_with_use_serial_batch_field(self):
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return
|
||||
|
||||
batch_item = make_item(
|
||||
"_Test Delivery Note Return Valuation WITH Batch Item",
|
||||
properties={
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"is_stock_item": 1,
|
||||
"batch_number_series": "BRTN-DNN-BIW-.#####",
|
||||
},
|
||||
).name
|
||||
|
||||
serial_item = make_item(
|
||||
"_Test Delivery Note Return Valuation WITH Serial Item",
|
||||
properties={"has_serial_no": 1, "is_stock_item": 1, "serial_no_series": "SRTN-DNN-TPW-.#####"},
|
||||
).name
|
||||
|
||||
batches = []
|
||||
serial_nos = []
|
||||
for qty, rate in {3: 300, 2: 100}.items():
|
||||
se = make_stock_entry(
|
||||
item_code=batch_item, target="_Test Warehouse - _TC", qty=qty, basic_rate=rate
|
||||
)
|
||||
batches.append(get_batch_from_bundle(se.items[0].serial_and_batch_bundle))
|
||||
|
||||
for qty, rate in {2: 100, 1: 50}.items():
|
||||
se = make_stock_entry(
|
||||
item_code=serial_item, target="_Test Warehouse - _TC", qty=qty, basic_rate=rate
|
||||
)
|
||||
serial_nos.extend(get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle))
|
||||
|
||||
dn = create_delivery_note(
|
||||
item_code=batch_item,
|
||||
qty=3,
|
||||
rate=1000,
|
||||
use_serial_batch_fields=1,
|
||||
batch_no=batches[0],
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
dn.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": batch_item,
|
||||
"qty": 2,
|
||||
"rate": 1000,
|
||||
"base_rate": 1000,
|
||||
"item_name": batch_item,
|
||||
"uom": dn.items[0].uom,
|
||||
"stock_uom": dn.items[0].uom,
|
||||
"conversion_factor": 1,
|
||||
"warehouse": dn.items[0].warehouse,
|
||||
"use_serial_batch_fields": 1,
|
||||
"batch_no": batches[1],
|
||||
},
|
||||
)
|
||||
|
||||
dn.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": serial_item,
|
||||
"qty": 2,
|
||||
"rate": 700,
|
||||
"base_rate": 700,
|
||||
"item_name": serial_item,
|
||||
"uom": "Nos",
|
||||
"stock_uom": "Nos",
|
||||
"conversion_factor": 1,
|
||||
"warehouse": dn.items[0].warehouse,
|
||||
"use_serial_batch_fields": 1,
|
||||
"serial_no": "\n".join(serial_nos[0:2]),
|
||||
},
|
||||
)
|
||||
|
||||
dn.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": serial_item,
|
||||
"qty": 1,
|
||||
"rate": 700,
|
||||
"base_rate": 700,
|
||||
"item_name": serial_item,
|
||||
"uom": "Nos",
|
||||
"stock_uom": "Nos",
|
||||
"conversion_factor": 1,
|
||||
"warehouse": dn.items[0].warehouse,
|
||||
"use_serial_batch_fields": 1,
|
||||
"serial_no": serial_nos[-1],
|
||||
},
|
||||
)
|
||||
|
||||
dn.save()
|
||||
dn.submit()
|
||||
dn.reload()
|
||||
|
||||
batch_no_valuation = defaultdict(float)
|
||||
serial_no_valuation = defaultdict(float)
|
||||
|
||||
for row in dn.items:
|
||||
if row.serial_and_batch_bundle:
|
||||
bundle_data = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
filters={"parent": row.serial_and_batch_bundle},
|
||||
fields=["incoming_rate", "serial_no", "batch_no"],
|
||||
)
|
||||
|
||||
for d in bundle_data:
|
||||
if d.batch_no:
|
||||
batch_no_valuation[d.batch_no] = d.incoming_rate
|
||||
elif d.serial_no:
|
||||
serial_no_valuation[d.serial_no] = d.incoming_rate
|
||||
|
||||
return_entry = make_sales_return(dn.name)
|
||||
|
||||
return_entry.save()
|
||||
return_entry.submit()
|
||||
return_entry.reload()
|
||||
|
||||
for row in return_entry.items:
|
||||
if row.item_code == batch_item:
|
||||
bundle_data = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
filters={"parent": row.serial_and_batch_bundle},
|
||||
fields=["incoming_rate", "batch_no"],
|
||||
)
|
||||
|
||||
for d in bundle_data:
|
||||
self.assertEqual(d.incoming_rate, batch_no_valuation[d.batch_no])
|
||||
else:
|
||||
bundle_data = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
filters={"parent": row.serial_and_batch_bundle},
|
||||
fields=["incoming_rate", "serial_no"],
|
||||
)
|
||||
|
||||
for d in bundle_data:
|
||||
self.assertEqual(d.incoming_rate, serial_no_valuation[d.serial_no])
|
||||
|
||||
def test_auto_set_serial_batch_for_draft_dn(self):
|
||||
frappe.db.set_single_value("Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 1)
|
||||
frappe.db.set_single_value("Stock Settings", "pick_serial_and_batch_based_on", "FIFO")
|
||||
|
||||
batch_item = make_item(
|
||||
"_Test Auto Set Serial Batch Draft DN",
|
||||
properties={
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"is_stock_item": 1,
|
||||
"batch_number_series": "TAS-BASD-.#####",
|
||||
},
|
||||
)
|
||||
|
||||
serial_item = make_item(
|
||||
"_Test Auto Set Serial Batch Draft DN Serial Item",
|
||||
properties={"has_serial_no": 1, "is_stock_item": 1, "serial_no_series": "TAS-SASD-.#####"},
|
||||
)
|
||||
|
||||
batch_serial_item = make_item(
|
||||
"_Test Auto Set Serial Batch Draft DN Batch Serial Item",
|
||||
properties={
|
||||
"has_batch_no": 1,
|
||||
"has_serial_no": 1,
|
||||
"is_stock_item": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "TAS-BSD-.#####",
|
||||
"serial_no_series": "TAS-SSD-.#####",
|
||||
},
|
||||
)
|
||||
|
||||
for item in [batch_item, serial_item, batch_serial_item]:
|
||||
make_stock_entry(item_code=item.name, target="_Test Warehouse - _TC", qty=5, basic_rate=100)
|
||||
|
||||
dn = create_delivery_note(
|
||||
item_code=batch_item,
|
||||
qty=5,
|
||||
rate=500,
|
||||
use_serial_batch_fields=1,
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
for item in [serial_item, batch_serial_item]:
|
||||
dn.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": item.name,
|
||||
"qty": 5,
|
||||
"rate": 500,
|
||||
"base_rate": 500,
|
||||
"item_name": item.name,
|
||||
"uom": "Nos",
|
||||
"stock_uom": "Nos",
|
||||
"conversion_factor": 1,
|
||||
"warehouse": dn.items[0].warehouse,
|
||||
"use_serial_batch_fields": 1,
|
||||
},
|
||||
)
|
||||
|
||||
dn.save()
|
||||
for row in dn.items:
|
||||
if row.item_code == batch_item.name:
|
||||
self.assertTrue(row.batch_no)
|
||||
|
||||
if row.item_code == serial_item.name:
|
||||
self.assertTrue(row.serial_no)
|
||||
|
||||
def test_delivery_note_return_for_batch_item_with_different_warehouse(self):
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
batch_item = make_item(
|
||||
"_Test Delivery Note Return Valuation WITH Batch Item",
|
||||
properties={
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"is_stock_item": 1,
|
||||
"batch_number_series": "BRTN-DNN-BIW-.#####",
|
||||
},
|
||||
).name
|
||||
|
||||
batches = []
|
||||
for qty, rate in {5: 300}.items():
|
||||
se = make_stock_entry(
|
||||
item_code=batch_item, target="_Test Warehouse - _TC", qty=qty, basic_rate=rate
|
||||
)
|
||||
batches.append(get_batch_from_bundle(se.items[0].serial_and_batch_bundle))
|
||||
|
||||
warehouse = create_warehouse("Sales Return Test Warehouse 1", company="_Test Company")
|
||||
|
||||
dn = create_delivery_note(
|
||||
item_code=batch_item,
|
||||
qty=5,
|
||||
rate=1000,
|
||||
use_serial_batch_fields=1,
|
||||
batch_no=batches[0],
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
self.assertEqual(dn.items[0].warehouse, "_Test Warehouse - _TC")
|
||||
|
||||
dn.save()
|
||||
dn.submit()
|
||||
dn.reload()
|
||||
|
||||
batch_no_valuation = defaultdict(float)
|
||||
|
||||
for row in dn.items:
|
||||
if row.serial_and_batch_bundle:
|
||||
bundle_data = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
filters={"parent": row.serial_and_batch_bundle},
|
||||
fields=["incoming_rate", "serial_no", "batch_no"],
|
||||
)
|
||||
|
||||
for d in bundle_data:
|
||||
if d.batch_no:
|
||||
batch_no_valuation[d.batch_no] = d.incoming_rate
|
||||
|
||||
return_entry = make_sales_return(dn.name)
|
||||
return_entry.items[0].warehouse = warehouse
|
||||
|
||||
return_entry.save()
|
||||
return_entry.submit()
|
||||
return_entry.reload()
|
||||
|
||||
for row in return_entry.items:
|
||||
self.assertEqual(row.warehouse, warehouse)
|
||||
bundle_data = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
filters={"parent": row.serial_and_batch_bundle},
|
||||
fields=["incoming_rate", "batch_no"],
|
||||
)
|
||||
|
||||
for d in bundle_data:
|
||||
self.assertEqual(d.incoming_rate, batch_no_valuation[d.batch_no])
|
||||
|
||||
def test_delivery_note_per_billed_after_return(self):
|
||||
from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
|
||||
|
||||
so = make_sales_order(qty=2)
|
||||
dn = make_delivery_note(so.name)
|
||||
dn.submit()
|
||||
self.assertEqual(dn.per_billed, 0)
|
||||
self.assertEqual(dn.status, "To Bill")
|
||||
|
||||
si = make_sales_invoice(dn.name)
|
||||
si.location = "Test Location"
|
||||
si.submit()
|
||||
|
||||
dn_return = create_delivery_note(is_return=1, return_against=dn.name, qty=-2, do_not_submit=True)
|
||||
dn_return.items[0].dn_detail = dn.items[0].name
|
||||
dn_return.submit()
|
||||
|
||||
returned = frappe.get_doc("Delivery Note", dn_return.name)
|
||||
returned.update_prevdoc_status()
|
||||
dn.load_from_db()
|
||||
self.assertEqual(dn.per_billed, 100)
|
||||
self.assertEqual(dn.per_returned, 100)
|
||||
self.assertEqual(returned.status, "Return")
|
||||
|
||||
def test_sales_return_for_product_bundle(self):
|
||||
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
rm_items = []
|
||||
for item_code, properties in {
|
||||
"_Packed Service Item": {"is_stock_item": 0},
|
||||
"_Packed FG Item New 1": {
|
||||
"is_stock_item": 1,
|
||||
"has_serial_no": 1,
|
||||
"serial_no_series": "SN-PACKED-1-.#####",
|
||||
},
|
||||
"_Packed FG Item New 2": {
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "BATCH-PACKED-2-.#####",
|
||||
},
|
||||
"_Packed FG Item New 3": {
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "BATCH-PACKED-3-.#####",
|
||||
"has_serial_no": 1,
|
||||
"serial_no_series": "SN-PACKED-3-.#####",
|
||||
},
|
||||
}.items():
|
||||
if not frappe.db.exists("Item", item_code):
|
||||
make_item(item_code, properties)
|
||||
|
||||
if item_code != "_Packed Service Item":
|
||||
rm_items.append(item_code)
|
||||
|
||||
for rate in [100, 200]:
|
||||
make_stock_entry(item=item_code, target="_Test Warehouse - _TC", qty=5, rate=rate)
|
||||
|
||||
make_product_bundle("_Packed Service Item", rm_items)
|
||||
dn = create_delivery_note(
|
||||
item_code="_Packed Service Item",
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
qty=5,
|
||||
)
|
||||
|
||||
dn.reload()
|
||||
|
||||
serial_batch_map = {}
|
||||
for row in dn.packed_items:
|
||||
self.assertTrue(row.serial_and_batch_bundle)
|
||||
if row.item_code not in serial_batch_map:
|
||||
serial_batch_map[row.item_code] = frappe._dict(
|
||||
{
|
||||
"serial_nos": [],
|
||||
"batches": defaultdict(int),
|
||||
"serial_no_valuation": defaultdict(float),
|
||||
"batch_no_valuation": defaultdict(float),
|
||||
}
|
||||
)
|
||||
|
||||
doc = frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle)
|
||||
for entry in doc.entries:
|
||||
if entry.serial_no:
|
||||
serial_batch_map[row.item_code].serial_nos.append(entry.serial_no)
|
||||
serial_batch_map[row.item_code].serial_no_valuation[entry.serial_no] = entry.incoming_rate
|
||||
if entry.batch_no:
|
||||
serial_batch_map[row.item_code].batches[entry.batch_no] += entry.qty
|
||||
serial_batch_map[row.item_code].batch_no_valuation[entry.batch_no] = entry.incoming_rate
|
||||
|
||||
dn1 = make_sales_return(dn.name)
|
||||
dn1.items[0].qty = -2
|
||||
dn1.submit()
|
||||
dn1.reload()
|
||||
|
||||
for row in dn1.packed_items:
|
||||
doc = frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle)
|
||||
for entry in doc.entries:
|
||||
if entry.serial_no:
|
||||
self.assertTrue(entry.serial_no in serial_batch_map[row.item_code].serial_nos)
|
||||
self.assertEqual(
|
||||
entry.incoming_rate,
|
||||
serial_batch_map[row.item_code].serial_no_valuation[entry.serial_no],
|
||||
)
|
||||
serial_batch_map[row.item_code].serial_nos.remove(entry.serial_no)
|
||||
serial_batch_map[row.item_code].serial_no_valuation.pop(entry.serial_no)
|
||||
|
||||
elif entry.batch_no:
|
||||
serial_batch_map[row.item_code].batches[entry.batch_no] += entry.qty
|
||||
self.assertTrue(entry.batch_no in serial_batch_map[row.item_code].batches)
|
||||
self.assertEqual(entry.qty, 2.0)
|
||||
self.assertEqual(
|
||||
entry.incoming_rate,
|
||||
serial_batch_map[row.item_code].batch_no_valuation[entry.batch_no],
|
||||
)
|
||||
|
||||
dn2 = make_sales_return(dn.name)
|
||||
dn2.items[0].qty = -3
|
||||
dn2.submit()
|
||||
dn2.reload()
|
||||
|
||||
for row in dn2.packed_items:
|
||||
doc = frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle)
|
||||
for entry in doc.entries:
|
||||
if entry.serial_no:
|
||||
self.assertTrue(entry.serial_no in serial_batch_map[row.item_code].serial_nos)
|
||||
self.assertEqual(
|
||||
entry.incoming_rate,
|
||||
serial_batch_map[row.item_code].serial_no_valuation[entry.serial_no],
|
||||
)
|
||||
serial_batch_map[row.item_code].serial_nos.remove(entry.serial_no)
|
||||
serial_batch_map[row.item_code].serial_no_valuation.pop(entry.serial_no)
|
||||
|
||||
elif entry.batch_no:
|
||||
serial_batch_map[row.item_code].batches[entry.batch_no] += entry.qty
|
||||
self.assertEqual(serial_batch_map[row.item_code].batches[entry.batch_no], 0.0)
|
||||
|
||||
self.assertTrue(entry.batch_no in serial_batch_map[row.item_code].batches)
|
||||
|
||||
self.assertEqual(entry.qty, 3.0)
|
||||
self.assertEqual(
|
||||
entry.incoming_rate,
|
||||
serial_batch_map[row.item_code].batch_no_valuation[entry.batch_no],
|
||||
)
|
||||
|
||||
<<<<<<< HEAD
|
||||
@change_settings("Stock Settings", {"allow_negative_stock": 0, "enable_stock_reservation": 1})
|
||||
def test_partial_delivery_note_against_reserved_stock(self):
|
||||
from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
|
||||
get_stock_reservation_entries_for_voucher,
|
||||
)
|
||||
|
||||
# create batch item
|
||||
batch_item = make_item(
|
||||
"_Test Batch Item For DN Reserve Check",
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "TBDNR.#####",
|
||||
},
|
||||
)
|
||||
serial_item = make_item(
|
||||
"_Test Serial Item For DN Reserve Check",
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
"has_serial_no": 1,
|
||||
"serial_no_series": "TSNDNR.#####",
|
||||
},
|
||||
)
|
||||
|
||||
company = "_Test Company"
|
||||
|
||||
warehouse = create_warehouse("Test Partial DN Reserved Stock", company=company)
|
||||
customer = "_Test Customer"
|
||||
|
||||
items = [batch_item.name, serial_item.name]
|
||||
|
||||
for idx, item in enumerate(items):
|
||||
# make inward entry for batch item
|
||||
se = make_stock_entry(item_code=item, purpose="Material Receipt", qty=10, to_warehouse=warehouse)
|
||||
sabb = se.items[0].serial_and_batch_bundle
|
||||
|
||||
batch_no = get_batch_from_bundle(sabb) if not idx else None
|
||||
serial_nos = get_serial_nos_from_bundle(sabb) if idx else None
|
||||
|
||||
# make sales order and reserve the quantites against the so
|
||||
so = make_sales_order(item_code=item, qty=10, rate=100, customer=customer, warehouse=warehouse)
|
||||
so.submit()
|
||||
so.create_stock_reservation_entries()
|
||||
so.reload()
|
||||
|
||||
# create a delivery note with partial quantity from resreved quantity
|
||||
dn = create_dn_against_so(so=so.name, delivered_qty=5, do_not_submit=True)
|
||||
dn.items[0].use_serial_batch_fields = 1
|
||||
if batch_no:
|
||||
dn.items[0].batch_no = batch_no
|
||||
else:
|
||||
dn.items[0].serial_no = "\n".join(serial_nos[:5])
|
||||
|
||||
dn.save()
|
||||
dn.submit()
|
||||
|
||||
against_sales_order = dn.items[0].against_sales_order
|
||||
so_detail = dn.items[0].so_detail
|
||||
|
||||
sre_details = get_stock_reservation_entries_for_voucher(
|
||||
so.doctype, against_sales_order, so_detail, ["reserved_qty", "delivered_qty", "status"]
|
||||
)
|
||||
|
||||
# check partially delivered reserved stock
|
||||
self.assertEqual(sre_details[0].status, "Partially Delivered")
|
||||
self.assertEqual(sre_details[0].reserved_qty, so.items[0].qty)
|
||||
self.assertEqual(sre_details[0].delivered_qty, dn.items[0].qty)
|
||||
=======
|
||||
def test_negative_stock_with_higher_precision(self):
|
||||
original_flt_precision = frappe.db.get_default("float_precision")
|
||||
frappe.db.set_single_value("System Settings", "float_precision", 7)
|
||||
|
||||
item_code = make_item(
|
||||
"Test Negative Stock High Precision Item", properties={"is_stock_item": 1, "valuation_rate": 1}
|
||||
).name
|
||||
dn = create_delivery_note(
|
||||
item_code=item_code,
|
||||
qty=0.0000010,
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
self.assertRaises(frappe.ValidationError, dn.submit)
|
||||
|
||||
frappe.db.set_single_value("System Settings", "float_precision", original_flt_precision)
|
||||
>>>>>>> 87be020c78 (fix: negative stock issue for higher precision)
|
||||
|
||||
>>>>>>> 1bbeecff12 (fix: negative stock issue for higher precision)
|
||||
|
||||
def create_delivery_note(**args):
|
||||
dn = frappe.new_doc("Delivery Note")
|
||||
|
||||
@@ -715,7 +715,11 @@ class update_entries_after:
|
||||
diff = self.wh_data.qty_after_transaction + flt(sle.actual_qty)
|
||||
diff = flt(diff, self.flt_precision) # respect system precision
|
||||
|
||||
if diff < 0 and abs(diff) > 0.0001:
|
||||
diff_threshold = 0.0001
|
||||
if self.flt_precision > 4:
|
||||
diff_threshold = 10 ** (-1 * self.flt_precision)
|
||||
|
||||
if diff < 0 and abs(diff) > diff_threshold:
|
||||
# negative stock!
|
||||
exc = sle.copy().update({"diff": diff})
|
||||
self.exceptions.setdefault(sle.warehouse, []).append(exc)
|
||||
|
||||
Reference in New Issue
Block a user