fix: valuation rate for sales / purchase return for serial / batch nos (#43925)

This commit is contained in:
rohitwaghchaure
2024-10-31 22:54:29 +05:30
committed by GitHub
parent c7d6923452
commit 01bb1612da
6 changed files with 643 additions and 13 deletions

View File

@@ -600,6 +600,10 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
if default_warehouse_for_sales_return: if default_warehouse_for_sales_return:
target_doc.warehouse = 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 ( if (
(source_doc.serial_no or source_doc.batch_no) (source_doc.serial_no or source_doc.batch_no)
and not source_doc.serial_and_batch_bundle 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`.`serial_no`",
"`tabSerial and Batch Entry`.`batch_no`", "`tabSerial and Batch Entry`.`batch_no`",
"`tabSerial and Batch Entry`.`qty`", "`tabSerial and Batch Entry`.`qty`",
"`tabSerial and Batch Entry`.`incoming_rate`",
"`tabSerial and Batch Bundle`.`voucher_detail_no`", "`tabSerial and Batch Bundle`.`voucher_detail_no`",
"`tabSerial and Batch Bundle`.`voucher_type`", "`tabSerial and Batch Bundle`.`voucher_type`",
"`tabSerial and Batch Bundle`.`voucher_no`", "`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: if key not in available_dict:
available_dict[key] = frappe._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 available_dict[key]["qty"] += row.qty
if row.serial_no: if row.serial_no:
available_dict[key]["serial_nos"][row.serial_no] += row.qty 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: elif row.batch_no:
available_dict[key]["batches"][row.batch_no] += row.qty available_dict[key]["batches"][row.batch_no] += row.qty
available_dict[key]["batches_valuation"][row.batch_no] = row.incoming_rate
return available_dict return available_dict
@@ -967,12 +980,13 @@ def get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=False
) )
) )
else: else:
fields = [ fields = ["serial_and_batch_bundle"]
"serial_and_batch_bundle",
]
if is_rejected: 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"] del filters["rejected_serial_and_batch_bundle"]
data = frappe.get_all( 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) warehouse = row.get(warehouse_field)
qty = abs(row.get(qty_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: if data.serial_nos:
available_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 available_serial_nos:
if parent_doc.doctype in ["Purchase Invoice", "Purchase Reecipt"]: 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: if len(available_serial_nos) > qty:
filterd_serial_batch["serial_nos"] = sorted(available_serial_nos[0 : cint(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, "warehouse": warehouse,
"serial_nos": data.get("serial_nos"), "serial_nos": data.get("serial_nos"),
"batches": data.get("batches"), "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_date": parent_doc.posting_date,
"posting_time": parent_doc.posting_time, "posting_time": parent_doc.posting_time,
"voucher_type": parent_doc.doctype, "voucher_type": parent_doc.doctype,

View File

@@ -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]]: def get_reference_ids(self, table_name, qty_field=None, bundle_field=None) -> tuple[str, list[str]]:
field = { field = {
"Sales Invoice": "sales_invoice_item", "Sales Invoice": "sales_invoice_item",

View File

@@ -3,6 +3,7 @@
import json import json
from collections import defaultdict
import frappe import frappe
from frappe.tests import IntegrationTestCase, UnitTestCase from frappe.tests import IntegrationTestCase, UnitTestCase
@@ -2116,6 +2117,264 @@ class TestDeliveryNote(IntegrationTestCase):
self.assertEqual(stock_value_difference, 100.0 * 5) 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): def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note") dn = frappe.new_doc("Delivery Note")
@@ -2143,6 +2402,9 @@ def create_delivery_note(**args):
if args.get("batch_no"): if args.get("batch_no"):
batches = frappe._dict({args.batch_no: qty}) batches = frappe._dict({args.batch_no: qty})
if args.get("batches"):
batches = frappe._dict(args.batches)
bundle_id = make_serial_batch_bundle( bundle_id = make_serial_batch_bundle(
frappe._dict( frappe._dict(
{ {

View File

@@ -3721,6 +3721,234 @@ class TestPurchaseReceipt(IntegrationTestCase):
self.assertEqual(pr.items[0].conversion_factor, 1.0) 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(): def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier

View File

@@ -197,7 +197,7 @@ class SerialandBatchBundle(Document):
def throw_error_message(self, message, exception=frappe.ValidationError): def throw_error_message(self, message, exception=frappe.ValidationError):
frappe.throw(_(message), exception, title=_("Error")) 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 [ if self.type_of_transaction not in ["Inward", "Outward"] or self.voucher_type in [
"Installation Note", "Installation Note",
"Job Card", "Job Card",
@@ -206,13 +206,70 @@ class SerialandBatchBundle(Document):
]: ]:
return 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( self.set_incoming_rate_for_outward_transaction(
row, save, allow_negative_stock=allow_negative_stock row, save, allow_negative_stock=allow_negative_stock
) )
else: else:
self.set_incoming_rate_for_inward_transaction(row, save) 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): def calculate_total_qty(self, save=True):
self.total_qty = 0.0 self.total_qty = 0.0
for d in self.entries: for d in self.entries:
@@ -327,6 +384,33 @@ class SerialandBatchBundle(Document):
return sle 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): def set_incoming_rate_for_inward_transaction(self, row=None, save=False):
valuation_field = "valuation_rate" valuation_field = "valuation_rate"
if self.voucher_type in ["Sales Invoice", "Delivery Note", "Quotation"]: 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) rate = frappe.db.get_value(child_table, self.voucher_detail_no, valuation_field)
for d in self.entries: 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 continue
d.incoming_rate = flt(rate, precision) d.incoming_rate = flt(rate, precision)
@@ -403,7 +489,7 @@ class SerialandBatchBundle(Document):
# If user has changed the rate in the child table # If user has changed the rate in the child table
if self.docstatus == 0: 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(): if self.docstatus == 0 and parent.get("is_return") and parent.is_new():
self.reset_qty(row, qty_field=qty_field) self.reset_qty(row, qty_field=qty_field)

View File

@@ -1088,6 +1088,8 @@ class SerialBatchCreation:
frappe.db.bulk_insert("Serial No", fields=fields, values=set(serial_nos_details)) frappe.db.bulk_insert("Serial No", fields=fields, values=set(serial_nos_details))
def set_serial_batch_entries(self, doc): def set_serial_batch_entries(self, doc):
incoming_rate = self.get("incoming_rate")
if self.get("serial_nos"): if self.get("serial_nos"):
serial_no_wise_batch = frappe._dict({}) serial_no_wise_batch = frappe._dict({})
if self.has_batch_no: if self.has_batch_no:
@@ -1095,30 +1097,54 @@ class SerialBatchCreation:
qty = -1 if self.type_of_transaction == "Outward" else 1 qty = -1 if self.type_of_transaction == "Outward" else 1
for serial_no in self.serial_nos: 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( doc.append(
"entries", "entries",
{ {
"serial_no": serial_no, "serial_no": serial_no,
"qty": qty, "qty": qty,
"batch_no": serial_no_wise_batch.get(serial_no) or self.get("batch_no"), "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"): elif self.get("batches"):
for batch_no, batch_qty in self.batches.items(): 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( doc.append(
"entries", "entries",
{ {
"batch_no": batch_no, "batch_no": batch_no,
"qty": batch_qty * (-1 if self.type_of_transaction == "Outward" else 1), "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): def create_batch(self):
from erpnext.stock.doctype.batch.batch import make_batch 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( return make_batch(
frappe._dict( frappe._dict(
{ {