fix: use serial/batch field for rejected items (#40327)

This commit is contained in:
rohitwaghchaure
2024-03-07 14:14:19 +05:30
committed by GitHub
parent 4b38139b44
commit 01856a6e9d
7 changed files with 289 additions and 26 deletions

View File

@@ -2108,6 +2108,92 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
return_pi.submit()
self.assertEqual(return_pi.docstatus, 1)
def test_purchase_invoice_with_use_serial_batch_field_for_rejected_qty(self):
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
batch_item = make_item(
"_Test Purchase Invoice Batch Item For Rejected Qty",
properties={"has_batch_no": 1, "create_new_batch": 1, "is_stock_item": 1},
).name
serial_item = make_item(
"_Test Purchase Invoice Serial Item for Rejected Qty",
properties={"has_serial_no": 1, "is_stock_item": 1},
).name
rej_warehouse = create_warehouse("_Test Purchase INV Warehouse For Rejected Qty")
batch_no = "BATCH-PI-BNU-TPRBI-0001"
serial_nos = ["SNU-PI-TPRSI-0001", "SNU-PI-TPRSI-0002", "SNU-PI-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()
pi = make_purchase_invoice(
item_code=batch_item,
received_qty=10,
qty=8,
rejected_qty=2,
update_stock=1,
rejected_warehouse=rej_warehouse,
use_serial_batch_fields=1,
batch_no=batch_no,
rate=100,
do_not_submit=1,
)
pi.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": pi.items[0].warehouse,
"rejected_warehouse": rej_warehouse,
"use_serial_batch_fields": 1,
"serial_no": "\n".join(serial_nos[:2]),
"rejected_serial_no": serial_nos[2],
},
)
pi.save()
pi.submit()
pi.reload()
for row in pi.items:
self.assertTrue(row.serial_and_batch_bundle)
self.assertTrue(row.rejected_serial_and_batch_bundle)
if row.item_code == batch_item:
self.assertEqual(row.batch_no, batch_no)
else:
self.assertEqual(row.serial_no, "\n".join(serial_nos[:2]))
self.assertEqual(row.rejected_serial_no, serial_nos[2])
def set_advance_flag(company, flag, default_account):
frappe.db.set_value(
@@ -2215,7 +2301,7 @@ def make_purchase_invoice(**args):
pi.cost_center = args.parent_cost_center
bundle_id = None
if args.get("batch_no") or args.get("serial_no"):
if not args.use_serial_batch_fields and ((args.get("batch_no") or args.get("serial_no"))):
batches = {}
qty = args.qty if args.qty is not None else 5
item_code = args.item or args.item_code or "_Test Item"
@@ -2262,6 +2348,9 @@ def make_purchase_invoice(**args):
"rejected_warehouse": args.rejected_warehouse or "",
"asset_location": args.location or "",
"allow_zero_valuation_rate": args.get("allow_zero_valuation_rate") or 0,
"use_serial_batch_fields": args.get("use_serial_batch_fields") or 0,
"batch_no": args.get("batch_no") if args.get("use_serial_batch_fields") else "",
"serial_no": args.get("serial_no") if args.get("use_serial_batch_fields") else "",
},
)

View File

@@ -159,9 +159,6 @@ class StockController(AccountsController):
row.serial_no = clean_serial_no_string(row.serial_no)
def make_bundle_using_old_serial_batch_fields(self, table_name=None):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
if self.get("_action") == "update_after_submit":
return
@@ -199,31 +196,21 @@ class StockController(AccountsController):
"voucher_detail_no": row.name,
"company": self.company,
"is_rejected": 1 if row.get("rejected_warehouse") else 0,
"serial_nos": get_serial_nos(row.serial_no) if row.serial_no else None,
"batch_no": row.batch_no,
"use_serial_batch_fields": row.use_serial_batch_fields,
"do_not_submit": True,
}
self.update_bundle_details(bundle_details, table_name, row)
sn_doc = SerialBatchCreation(bundle_details).make_serial_and_batch_bundle()
if row.qty:
self.update_bundle_details(bundle_details, table_name, row)
self.create_serial_batch_bundle(bundle_details, row)
if sn_doc.is_rejected:
row.rejected_serial_and_batch_bundle = sn_doc.name
row.db_set(
{
"rejected_serial_and_batch_bundle": sn_doc.name,
}
)
else:
row.serial_and_batch_bundle = sn_doc.name
row.db_set(
{
"serial_and_batch_bundle": sn_doc.name,
}
)
if row.get("rejected_qty"):
self.update_bundle_details(bundle_details, table_name, row, is_rejected=True)
self.create_serial_batch_bundle(bundle_details, row)
def update_bundle_details(self, bundle_details, table_name, row, is_rejected=False):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
def update_bundle_details(self, bundle_details, table_name, row):
# Since qty field is different for different doctypes
qty = row.get("qty")
warehouse = row.get("warehouse")
@@ -242,15 +229,37 @@ class StockController(AccountsController):
qty = row.transfer_qty
warehouse = row.s_warehouse or row.t_warehouse
serial_nos = row.serial_no
if is_rejected:
serial_nos = row.get("rejected_serial_no")
type_of_transaction = "Inward" if not self.is_return else "Outward"
qty = row.get("rejected_qty")
warehouse = row.get("rejected_warehouse")
bundle_details.update(
{
"qty": qty,
"is_rejected": is_rejected,
"type_of_transaction": type_of_transaction,
"warehouse": warehouse,
"batches": frappe._dict({row.batch_no: qty}) if row.batch_no else None,
"serial_nos": get_serial_nos(serial_nos) if serial_nos else None,
"batch_no": row.batch_no,
}
)
def create_serial_batch_bundle(self, bundle_details, row):
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
sn_doc = SerialBatchCreation(bundle_details).make_serial_and_batch_bundle()
field = "serial_and_batch_bundle"
if bundle_details.get("is_rejected"):
field = "rejected_serial_and_batch_bundle"
row.set(field, sn_doc.name)
row.db_set({field: sn_doc.name})
def validate_serial_nos_and_batches_with_bundle(self, row):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos

View File

@@ -859,6 +859,7 @@ class SubcontractingController(StockController):
item,
{
"warehouse": item.rejected_warehouse,
"serial_and_batch_bundle": item.get("rejected_serial_and_batch_bundle"),
"actual_qty": flt(item.rejected_qty) * flt(item.conversion_factor),
"incoming_rate": 0.0,
},

View File

@@ -2477,6 +2477,88 @@ class TestPurchaseReceipt(FrappeTestCase):
pr.reload()
self.assertEqual(pr.per_billed, 100)
def test_purchase_receipt_with_use_serial_batch_field_for_rejected_qty(self):
batch_item = make_item(
"_Test Purchase Receipt Batch Item For Rejected Qty",
properties={"has_batch_no": 1, "create_new_batch": 1, "is_stock_item": 1},
).name
serial_item = make_item(
"_Test Purchase Receipt Serial Item for Rejected Qty",
properties={"has_serial_no": 1, "is_stock_item": 1},
).name
rej_warehouse = create_warehouse("_Test Purchase Warehouse For Rejected Qty")
batch_no = "BATCH-BNU-TPRBI-0001"
serial_nos = ["SNU-TPRSI-0001", "SNU-TPRSI-0002", "SNU-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,
use_serial_batch_fields=1,
batch_no=batch_no,
rate=100,
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,
"rejected_warehouse": rej_warehouse,
"use_serial_batch_fields": 1,
"serial_no": "\n".join(serial_nos[:2]),
"rejected_serial_no": serial_nos[2],
},
)
pr.save()
pr.submit()
pr.reload()
for row in pr.items:
self.assertTrue(row.serial_and_batch_bundle)
self.assertTrue(row.rejected_serial_and_batch_bundle)
if row.item_code == batch_item:
self.assertEqual(row.batch_no, batch_no)
else:
self.assertEqual(row.serial_no, "\n".join(serial_nos[:2]))
self.assertEqual(row.rejected_serial_no, serial_nos[2])
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier

View File

@@ -893,6 +893,9 @@ class update_entries_after(object):
query.run()
def calculate_valuation_for_serial_batch_bundle(self, sle):
if not frappe.db.exists("Serial and Batch Bundle", sle.serial_and_batch_bundle):
return
doc = frappe.get_cached_doc("Serial and Batch Bundle", sle.serial_and_batch_bundle)
doc.set_incoming_rate(save=True, allow_negative_stock=self.allow_negative_stock)

View File

@@ -1132,6 +1132,86 @@ class TestSubcontractingReceipt(FrappeTestCase):
scr.reload()
self.assertTrue(scr.items[0].serial_and_batch_bundle)
def test_use_serial_batch_fields_for_subcontracting_receipt_with_rejected_qty(self):
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
fg_item = make_item(
"Test Subcontracted Item With Batch No for Rejected Qty",
properties={
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "BATCH-REJ-BNGS-.####",
"is_sub_contracted_item": 1,
},
).name
make_item(
"Test Subcontracted Item With Batch No Service Item 2",
properties={"is_stock_item": 0},
)
make_bom(
item=fg_item,
raw_materials=[
make_item(
"Test Subcontracted Item With Batch No RM Item 2",
properties={
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "BATCH-REJ-RM-BNGS-.####",
},
).name
],
)
service_items = [
{
"warehouse": "_Test Warehouse - _TC",
"item_code": "Test Subcontracted Item With Batch No Service Item 2",
"qty": 10,
"rate": 100,
"fg_item": fg_item,
"fg_item_qty": 10,
},
]
sco = get_subcontracting_order(service_items=service_items)
rm_items = get_rm_items(sco.supplied_items)
itemwise_details = make_stock_in_entry(rm_items=rm_items)
make_stock_transfer_entry(
sco_no=sco.name,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
batch_no = "BATCH-REJ-BNGS-0001"
if not frappe.db.exists("Batch", batch_no):
frappe.get_doc(
{
"doctype": "Batch",
"batch_id": batch_no,
"item": fg_item,
}
).insert()
rej_warehouse = create_warehouse("_Test Subcontract Warehouse For Rejected Qty")
scr = make_subcontracting_receipt(sco.name)
self.assertFalse(scr.items[0].serial_and_batch_bundle)
scr.items[0].use_serial_batch_fields = 1
scr.items[0].batch_no = batch_no
scr.items[0].received_qty = 10
scr.items[0].rejected_qty = 2
scr.items[0].qty = 8
scr.items[0].rejected_warehouse = rej_warehouse
scr.save()
scr.submit()
scr.reload()
self.assertTrue(scr.items[0].serial_and_batch_bundle)
self.assertTrue(scr.items[0].rejected_serial_and_batch_bundle)
def make_return_subcontracting_receipt(**args):
args = frappe._dict(args)

View File

@@ -335,8 +335,7 @@
"fieldtype": "Small Text",
"label": "Rejected Serial No",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
"print_hide": 1
},
{
"fieldname": "subcontracting_order_item",
@@ -569,7 +568,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-02-04 16:23:30.374865",
"modified": "2024-03-07 11:43:38.954262",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Receipt Item",