fix: valuation rate for sales / purchase return for serial / batch nos (#43925)
This commit is contained in:
@@ -600,6 +600,10 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
|
||||
if default_warehouse_for_sales_return:
|
||||
target_doc.warehouse = default_warehouse_for_sales_return
|
||||
|
||||
if not source_doc.use_serial_batch_fields and source_doc.serial_and_batch_bundle:
|
||||
target_doc.serial_no = None
|
||||
target_doc.batch_no = None
|
||||
|
||||
if (
|
||||
(source_doc.serial_no or source_doc.batch_no)
|
||||
and not source_doc.serial_and_batch_bundle
|
||||
@@ -902,6 +906,7 @@ def get_serial_batches_based_on_bundle(field, _bundle_ids):
|
||||
"`tabSerial and Batch Entry`.`serial_no`",
|
||||
"`tabSerial and Batch Entry`.`batch_no`",
|
||||
"`tabSerial and Batch Entry`.`qty`",
|
||||
"`tabSerial and Batch Entry`.`incoming_rate`",
|
||||
"`tabSerial and Batch Bundle`.`voucher_detail_no`",
|
||||
"`tabSerial and Batch Bundle`.`voucher_type`",
|
||||
"`tabSerial and Batch Bundle`.`voucher_no`",
|
||||
@@ -923,15 +928,23 @@ def get_serial_batches_based_on_bundle(field, _bundle_ids):
|
||||
|
||||
if key not in available_dict:
|
||||
available_dict[key] = frappe._dict(
|
||||
{"qty": 0.0, "serial_nos": defaultdict(float), "batches": defaultdict(float)}
|
||||
{
|
||||
"qty": 0.0,
|
||||
"serial_nos": defaultdict(float),
|
||||
"batches": defaultdict(float),
|
||||
"serial_nos_valuation": defaultdict(float),
|
||||
"batches_valuation": defaultdict(float),
|
||||
}
|
||||
)
|
||||
|
||||
available_dict[key]["qty"] += row.qty
|
||||
|
||||
if row.serial_no:
|
||||
available_dict[key]["serial_nos"][row.serial_no] += row.qty
|
||||
available_dict[key]["serial_nos_valuation"][row.serial_no] = row.incoming_rate
|
||||
elif row.batch_no:
|
||||
available_dict[key]["batches"][row.batch_no] += row.qty
|
||||
available_dict[key]["batches_valuation"][row.batch_no] = row.incoming_rate
|
||||
|
||||
return available_dict
|
||||
|
||||
@@ -967,12 +980,13 @@ def get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=False
|
||||
)
|
||||
)
|
||||
else:
|
||||
fields = [
|
||||
"serial_and_batch_bundle",
|
||||
]
|
||||
fields = ["serial_and_batch_bundle"]
|
||||
|
||||
if is_rejected:
|
||||
fields.extend(["rejected_serial_and_batch_bundle", "return_qty_from_rejected_warehouse"])
|
||||
fields.append("rejected_serial_and_batch_bundle")
|
||||
|
||||
if doctype == "Purchase Receipt Item":
|
||||
fields.append("return_qty_from_rejected_warehouse")
|
||||
|
||||
del filters["rejected_serial_and_batch_bundle"]
|
||||
data = frappe.get_all(
|
||||
@@ -1006,7 +1020,14 @@ def filter_serial_batches(parent_doc, data, row, warehouse_field=None, qty_field
|
||||
warehouse = row.get(warehouse_field)
|
||||
qty = abs(row.get(qty_field))
|
||||
|
||||
filterd_serial_batch = frappe._dict({"serial_nos": [], "batches": defaultdict(float)})
|
||||
filterd_serial_batch = frappe._dict(
|
||||
{
|
||||
"serial_nos": [],
|
||||
"batches": defaultdict(float),
|
||||
"serial_nos_valuation": data.get("serial_nos_valuation"),
|
||||
"batches_valuation": data.get("batches_valuation"),
|
||||
}
|
||||
)
|
||||
|
||||
if data.serial_nos:
|
||||
available_serial_nos = []
|
||||
@@ -1016,7 +1037,7 @@ def filter_serial_batches(parent_doc, data, row, warehouse_field=None, qty_field
|
||||
|
||||
if available_serial_nos:
|
||||
if parent_doc.doctype in ["Purchase Invoice", "Purchase Reecipt"]:
|
||||
available_serial_nos = get_available_serial_nos(available_serial_nos)
|
||||
available_serial_nos = get_available_serial_nos(available_serial_nos, warehouse)
|
||||
|
||||
if len(available_serial_nos) > qty:
|
||||
filterd_serial_batch["serial_nos"] = sorted(available_serial_nos[0 : cint(qty)])
|
||||
@@ -1101,6 +1122,8 @@ def make_serial_batch_bundle_for_return(data, child_doc, parent_doc, warehouse_f
|
||||
"warehouse": warehouse,
|
||||
"serial_nos": data.get("serial_nos"),
|
||||
"batches": data.get("batches"),
|
||||
"serial_nos_valuation": data.get("serial_nos_valuation"),
|
||||
"batches_valuation": data.get("batches_valuation"),
|
||||
"posting_date": parent_doc.posting_date,
|
||||
"posting_time": parent_doc.posting_time,
|
||||
"voucher_type": parent_doc.doctype,
|
||||
|
||||
@@ -334,6 +334,11 @@ class StockController(AccountsController):
|
||||
}
|
||||
)
|
||||
|
||||
if self.doctype in ["Sales Invoice", "Delivery Note"]:
|
||||
row.db_set(
|
||||
"incoming_rate", frappe.db.get_value("Serial and Batch Bundle", bundle, "avg_rate")
|
||||
)
|
||||
|
||||
def get_reference_ids(self, table_name, qty_field=None, bundle_field=None) -> tuple[str, list[str]]:
|
||||
field = {
|
||||
"Sales Invoice": "sales_invoice_item",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
import json
|
||||
from collections import defaultdict
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||
@@ -2116,6 +2117,264 @@ class TestDeliveryNote(IntegrationTestCase):
|
||||
|
||||
self.assertEqual(stock_value_difference, 100.0 * 5)
|
||||
|
||||
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 create_delivery_note(**args):
|
||||
dn = frappe.new_doc("Delivery Note")
|
||||
@@ -2143,6 +2402,9 @@ def create_delivery_note(**args):
|
||||
if args.get("batch_no"):
|
||||
batches = frappe._dict({args.batch_no: qty})
|
||||
|
||||
if args.get("batches"):
|
||||
batches = frappe._dict(args.batches)
|
||||
|
||||
bundle_id = make_serial_batch_bundle(
|
||||
frappe._dict(
|
||||
{
|
||||
|
||||
@@ -3721,6 +3721,234 @@ class TestPurchaseReceipt(IntegrationTestCase):
|
||||
|
||||
self.assertEqual(pr.items[0].conversion_factor, 1.0)
|
||||
|
||||
def test_purchase_receipt_return_valuation_without_use_serial_batch_field(self):
|
||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return
|
||||
|
||||
batch_item = make_item(
|
||||
"_Test Purchase Receipt Return Valuation Batch Item",
|
||||
properties={
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"is_stock_item": 1,
|
||||
"batch_number_series": "BRTN-TPRBI-.#####",
|
||||
},
|
||||
).name
|
||||
|
||||
serial_item = make_item(
|
||||
"_Test Purchase Receipt Return Valuation Serial Item",
|
||||
properties={"has_serial_no": 1, "is_stock_item": 1, "serial_no_series": "SRTN-TPRSI-.#####"},
|
||||
).name
|
||||
|
||||
rej_warehouse = create_warehouse("_Test Purchase Warehouse For Rejected Qty")
|
||||
|
||||
pr = make_purchase_receipt(
|
||||
item_code=batch_item,
|
||||
received_qty=10,
|
||||
qty=8,
|
||||
rejected_qty=2,
|
||||
rejected_warehouse=rej_warehouse,
|
||||
rate=300,
|
||||
do_not_submit=1,
|
||||
use_serial_batch_fields=0,
|
||||
)
|
||||
|
||||
pr.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": serial_item,
|
||||
"qty": 2,
|
||||
"rate": 100,
|
||||
"base_rate": 100,
|
||||
"item_name": serial_item,
|
||||
"uom": "Nos",
|
||||
"stock_uom": "Nos",
|
||||
"conversion_factor": 1,
|
||||
"rejected_qty": 1,
|
||||
"warehouse": pr.items[0].warehouse,
|
||||
"use_serial_batch_fields": 0,
|
||||
"rejected_warehouse": rej_warehouse,
|
||||
},
|
||||
)
|
||||
|
||||
pr.save()
|
||||
pr.submit()
|
||||
pr.reload()
|
||||
|
||||
batch_no = get_batch_from_bundle(pr.items[0].serial_and_batch_bundle)
|
||||
rejected_batch_no = get_batch_from_bundle(pr.items[0].rejected_serial_and_batch_bundle)
|
||||
|
||||
self.assertEqual(batch_no, rejected_batch_no)
|
||||
|
||||
return_entry = make_purchase_return(pr.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},
|
||||
pluck="incoming_rate",
|
||||
)
|
||||
|
||||
for incoming_rate in bundle_data:
|
||||
self.assertEqual(incoming_rate, 300.00)
|
||||
else:
|
||||
bundle_data = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
filters={"parent": row.serial_and_batch_bundle},
|
||||
pluck="incoming_rate",
|
||||
)
|
||||
|
||||
for incoming_rate in bundle_data:
|
||||
self.assertEqual(incoming_rate, 100.00)
|
||||
|
||||
for row in return_entry.items:
|
||||
if row.item_code == batch_item:
|
||||
bundle_data = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
filters={"parent": row.rejected_serial_and_batch_bundle},
|
||||
pluck="incoming_rate",
|
||||
)
|
||||
|
||||
for incoming_rate in bundle_data:
|
||||
self.assertEqual(incoming_rate, 0)
|
||||
else:
|
||||
bundle_data = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
filters={"parent": row.rejected_serial_and_batch_bundle},
|
||||
pluck="incoming_rate",
|
||||
)
|
||||
|
||||
for incoming_rate in bundle_data:
|
||||
self.assertEqual(incoming_rate, 0)
|
||||
|
||||
def test_purchase_receipt_return_valuation_with_use_serial_batch_field(self):
|
||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return
|
||||
|
||||
batch_item = make_item(
|
||||
"_Test Purchase Receipt Return Valuation With Batch Item",
|
||||
properties={"has_batch_no": 1, "create_new_batch": 1, "is_stock_item": 1},
|
||||
).name
|
||||
|
||||
serial_item = make_item(
|
||||
"_Test Purchase Receipt Return Valuation With Serial Item",
|
||||
properties={"has_serial_no": 1, "is_stock_item": 1},
|
||||
).name
|
||||
|
||||
rej_warehouse = create_warehouse("_Test Purchase Warehouse For Rejected Qty")
|
||||
|
||||
batch_no = "BATCH-RTN-BNU-TPRBI-0001"
|
||||
serial_nos = ["SNU-RTN-TPRSI-0001", "SNU-RTN-TPRSI-0002", "SNU-RTN-TPRSI-0003"]
|
||||
|
||||
if not frappe.db.exists("Batch", batch_no):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Batch",
|
||||
"batch_id": batch_no,
|
||||
"item": batch_item,
|
||||
}
|
||||
).insert()
|
||||
|
||||
for serial_no in serial_nos:
|
||||
if not frappe.db.exists("Serial No", serial_no):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Serial No",
|
||||
"item_code": serial_item,
|
||||
"serial_no": serial_no,
|
||||
}
|
||||
).insert()
|
||||
|
||||
pr = make_purchase_receipt(
|
||||
item_code=batch_item,
|
||||
received_qty=10,
|
||||
qty=8,
|
||||
rejected_qty=2,
|
||||
rejected_warehouse=rej_warehouse,
|
||||
batch_no=batch_no,
|
||||
use_serial_batch_fields=1,
|
||||
rate=300,
|
||||
do_not_submit=1,
|
||||
)
|
||||
|
||||
pr.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": serial_item,
|
||||
"qty": 2,
|
||||
"rate": 100,
|
||||
"base_rate": 100,
|
||||
"item_name": serial_item,
|
||||
"uom": "Nos",
|
||||
"stock_uom": "Nos",
|
||||
"conversion_factor": 1,
|
||||
"rejected_qty": 1,
|
||||
"warehouse": pr.items[0].warehouse,
|
||||
"use_serial_batch_fields": 1,
|
||||
"rejected_warehouse": rej_warehouse,
|
||||
"serial_no": "\n".join(serial_nos[:2]),
|
||||
"rejected_serial_no": serial_nos[2],
|
||||
},
|
||||
)
|
||||
|
||||
pr.save()
|
||||
pr.submit()
|
||||
pr.reload()
|
||||
|
||||
batch_no = get_batch_from_bundle(pr.items[0].serial_and_batch_bundle)
|
||||
rejected_batch_no = get_batch_from_bundle(pr.items[0].rejected_serial_and_batch_bundle)
|
||||
|
||||
self.assertEqual(batch_no, rejected_batch_no)
|
||||
|
||||
return_entry = make_purchase_return(pr.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},
|
||||
pluck="incoming_rate",
|
||||
)
|
||||
|
||||
for incoming_rate in bundle_data:
|
||||
self.assertEqual(incoming_rate, 300.00)
|
||||
else:
|
||||
bundle_data = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
filters={"parent": row.serial_and_batch_bundle},
|
||||
pluck="incoming_rate",
|
||||
)
|
||||
|
||||
for incoming_rate in bundle_data:
|
||||
self.assertEqual(incoming_rate, 100.00)
|
||||
|
||||
for row in return_entry.items:
|
||||
if row.item_code == batch_item:
|
||||
bundle_data = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
filters={"parent": row.rejected_serial_and_batch_bundle},
|
||||
pluck="incoming_rate",
|
||||
)
|
||||
|
||||
for incoming_rate in bundle_data:
|
||||
self.assertEqual(incoming_rate, 0)
|
||||
else:
|
||||
bundle_data = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
filters={"parent": row.rejected_serial_and_batch_bundle},
|
||||
pluck="incoming_rate",
|
||||
)
|
||||
|
||||
for incoming_rate in bundle_data:
|
||||
self.assertEqual(incoming_rate, 0)
|
||||
|
||||
|
||||
def prepare_data_for_internal_transfer():
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||
|
||||
@@ -197,7 +197,7 @@ class SerialandBatchBundle(Document):
|
||||
def throw_error_message(self, message, exception=frappe.ValidationError):
|
||||
frappe.throw(_(message), exception, title=_("Error"))
|
||||
|
||||
def set_incoming_rate(self, row=None, save=False, allow_negative_stock=False):
|
||||
def set_incoming_rate(self, parent=None, row=None, save=False, allow_negative_stock=False):
|
||||
if self.type_of_transaction not in ["Inward", "Outward"] or self.voucher_type in [
|
||||
"Installation Note",
|
||||
"Job Card",
|
||||
@@ -206,13 +206,70 @@ class SerialandBatchBundle(Document):
|
||||
]:
|
||||
return
|
||||
|
||||
if self.type_of_transaction == "Outward":
|
||||
if return_aginst := self.get_return_aginst(parent=parent):
|
||||
self.set_valuation_rate_for_return_entry(return_aginst, save)
|
||||
elif self.type_of_transaction == "Outward":
|
||||
self.set_incoming_rate_for_outward_transaction(
|
||||
row, save, allow_negative_stock=allow_negative_stock
|
||||
)
|
||||
else:
|
||||
self.set_incoming_rate_for_inward_transaction(row, save)
|
||||
|
||||
def set_valuation_rate_for_return_entry(self, return_aginst, save=False):
|
||||
if valuation_details := self.get_valuation_rate_for_return_entry(return_aginst):
|
||||
for row in self.entries:
|
||||
if row.serial_no:
|
||||
valuation_rate = valuation_details["serial_nos"].get(row.serial_no)
|
||||
else:
|
||||
valuation_rate = valuation_details["batches"].get(row.batch_no)
|
||||
|
||||
row.incoming_rate = valuation_rate
|
||||
row.stock_value_difference = flt(row.qty) * flt(row.incoming_rate)
|
||||
|
||||
if save:
|
||||
row.db_set(
|
||||
{
|
||||
"incoming_rate": row.incoming_rate,
|
||||
"stock_value_difference": row.stock_value_difference,
|
||||
}
|
||||
)
|
||||
|
||||
def get_valuation_rate_for_return_entry(self, return_aginst):
|
||||
valuation_details = frappe._dict(
|
||||
{
|
||||
"serial_nos": defaultdict(float),
|
||||
"batches": defaultdict(float),
|
||||
}
|
||||
)
|
||||
|
||||
bundle_data = frappe.get_all(
|
||||
"Serial and Batch Bundle",
|
||||
fields=[
|
||||
"`tabSerial and Batch Entry`.`serial_no`",
|
||||
"`tabSerial and Batch Entry`.`batch_no`",
|
||||
"`tabSerial and Batch Entry`.`incoming_rate`",
|
||||
],
|
||||
filters=[
|
||||
["Serial and Batch Bundle", "voucher_no", "=", return_aginst],
|
||||
["Serial and Batch Entry", "docstatus", "=", 1],
|
||||
["Serial and Batch Bundle", "is_cancelled", "=", 0],
|
||||
["Serial and Batch Bundle", "item_code", "=", self.item_code],
|
||||
["Serial and Batch Bundle", "warehouse", "=", self.warehouse],
|
||||
],
|
||||
order_by="`tabSerial and Batch Bundle`.`creation`, `tabSerial and Batch Entry`.`idx`",
|
||||
)
|
||||
|
||||
if not bundle_data:
|
||||
return {}
|
||||
|
||||
for row in bundle_data:
|
||||
if row.serial_no:
|
||||
valuation_details["serial_nos"][row.serial_no] = row.incoming_rate
|
||||
else:
|
||||
valuation_details["batches"][row.batch_no] = row.incoming_rate
|
||||
|
||||
return valuation_details
|
||||
|
||||
def calculate_total_qty(self, save=True):
|
||||
self.total_qty = 0.0
|
||||
for d in self.entries:
|
||||
@@ -327,6 +384,33 @@ class SerialandBatchBundle(Document):
|
||||
|
||||
return sle
|
||||
|
||||
def get_return_aginst(self, parent=None):
|
||||
return_aginst = None
|
||||
|
||||
if parent and parent.get("is_return") and parent.get("return_against"):
|
||||
return parent.get("return_against")
|
||||
|
||||
if (
|
||||
self.voucher_type
|
||||
in [
|
||||
"Delivery Note",
|
||||
"Sales Invoice",
|
||||
"Purchase Invoice",
|
||||
"Purchase Receipt",
|
||||
"POS Invoice",
|
||||
"Subcontracting Receipt",
|
||||
]
|
||||
and self.voucher_type
|
||||
and self.voucher_no
|
||||
):
|
||||
voucher_details = frappe.db.get_value(
|
||||
self.voucher_type, self.voucher_no, ["is_return", "return_against"], as_dict=True
|
||||
)
|
||||
if voucher_details and voucher_details.get("is_return") and voucher_details.get("return_against"):
|
||||
return voucher_details.get("return_against")
|
||||
|
||||
return return_aginst
|
||||
|
||||
def set_incoming_rate_for_inward_transaction(self, row=None, save=False):
|
||||
valuation_field = "valuation_rate"
|
||||
if self.voucher_type in ["Sales Invoice", "Delivery Note", "Quotation"]:
|
||||
@@ -354,7 +438,9 @@ class SerialandBatchBundle(Document):
|
||||
rate = frappe.db.get_value(child_table, self.voucher_detail_no, valuation_field)
|
||||
|
||||
for d in self.entries:
|
||||
if (d.incoming_rate == rate) and d.qty and d.stock_value_difference:
|
||||
if self.is_rejected:
|
||||
rate = 0.0
|
||||
elif (d.incoming_rate == rate) and d.qty and d.stock_value_difference:
|
||||
continue
|
||||
|
||||
d.incoming_rate = flt(rate, precision)
|
||||
@@ -403,7 +489,7 @@ class SerialandBatchBundle(Document):
|
||||
|
||||
# If user has changed the rate in the child table
|
||||
if self.docstatus == 0:
|
||||
self.set_incoming_rate(save=True, row=row)
|
||||
self.set_incoming_rate(parent=parent, row=row, save=True)
|
||||
|
||||
if self.docstatus == 0 and parent.get("is_return") and parent.is_new():
|
||||
self.reset_qty(row, qty_field=qty_field)
|
||||
|
||||
@@ -1088,6 +1088,8 @@ class SerialBatchCreation:
|
||||
frappe.db.bulk_insert("Serial No", fields=fields, values=set(serial_nos_details))
|
||||
|
||||
def set_serial_batch_entries(self, doc):
|
||||
incoming_rate = self.get("incoming_rate")
|
||||
|
||||
if self.get("serial_nos"):
|
||||
serial_no_wise_batch = frappe._dict({})
|
||||
if self.has_batch_no:
|
||||
@@ -1095,30 +1097,54 @@ class SerialBatchCreation:
|
||||
|
||||
qty = -1 if self.type_of_transaction == "Outward" else 1
|
||||
for serial_no in self.serial_nos:
|
||||
if self.get("serial_nos_valuation"):
|
||||
incoming_rate = self.get("serial_nos_valuation").get(serial_no)
|
||||
|
||||
doc.append(
|
||||
"entries",
|
||||
{
|
||||
"serial_no": serial_no,
|
||||
"qty": qty,
|
||||
"batch_no": serial_no_wise_batch.get(serial_no) or self.get("batch_no"),
|
||||
"incoming_rate": self.get("incoming_rate"),
|
||||
"incoming_rate": incoming_rate,
|
||||
},
|
||||
)
|
||||
|
||||
elif self.get("batches"):
|
||||
for batch_no, batch_qty in self.batches.items():
|
||||
if self.get("batches_valuation"):
|
||||
incoming_rate = self.get("batches_valuation").get(batch_no)
|
||||
|
||||
doc.append(
|
||||
"entries",
|
||||
{
|
||||
"batch_no": batch_no,
|
||||
"qty": batch_qty * (-1 if self.type_of_transaction == "Outward" else 1),
|
||||
"incoming_rate": self.get("incoming_rate"),
|
||||
"incoming_rate": incoming_rate,
|
||||
},
|
||||
)
|
||||
|
||||
def create_batch(self):
|
||||
from erpnext.stock.doctype.batch.batch import make_batch
|
||||
|
||||
if self.is_rejected:
|
||||
bundle = frappe.db.get_value(
|
||||
"Serial and Batch Bundle",
|
||||
{
|
||||
"voucher_no": self.voucher_no,
|
||||
"voucher_type": self.voucher_type,
|
||||
"voucher_detail_no": self.voucher_detail_no,
|
||||
"is_rejected": 0,
|
||||
"docstatus": 1,
|
||||
"is_cancelled": 0,
|
||||
},
|
||||
"name",
|
||||
)
|
||||
|
||||
if bundle:
|
||||
if batch_no := frappe.db.get_value("Serial and Batch Entry", {"parent": bundle}, "batch_no"):
|
||||
return batch_no
|
||||
|
||||
return make_batch(
|
||||
frappe._dict(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user