Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: use serial batch fields for packed items #40140

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion erpnext/accounts/doctype/sales_invoice/sales_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,11 @@ def on_submit(self):
# Updating stock ledger should always be called after updating prevdoc status,
# because updating reserved qty in bin depends upon updated delivered qty in SO
if self.update_stock == 1:
self.make_bundle_using_old_serial_batch_fields()
for table_name in ["items", "packed_items"]:
if not self.get(table_name):
continue

self.make_bundle_using_old_serial_batch_fields(table_name)
self.update_stock_ledger()

# this sequence because outstanding may get -ve
Expand Down
3 changes: 3 additions & 0 deletions erpnext/controllers/selling_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,9 @@ def set_default_income_account_for_item(obj):
def get_serial_and_batch_bundle(child, parent):
from erpnext.stock.serial_batch_bundle import SerialBatchCreation

if child.get("use_serial_batch_fields"):
return

if not frappe.db.get_single_value(
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward"
):
Expand Down
12 changes: 10 additions & 2 deletions erpnext/controllers/stock_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def clean_serial_nos(self):
# remove extra whitespace and store one serial no on each line
row.serial_no = clean_serial_no_string(row.serial_no)

def make_bundle_using_old_serial_batch_fields(self):
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

Expand All @@ -169,7 +169,9 @@ def make_bundle_using_old_serial_batch_fields(self):
if frappe.flags.in_test and frappe.flags.use_serial_and_batch_fields:
return

table_name = "items"
if not table_name:
table_name = "items"

if self.doctype == "Asset Capitalization":
table_name = "stock_items"

Expand All @@ -192,6 +194,12 @@ def make_bundle_using_old_serial_batch_fields(self):
qty = row.qty
type_of_transaction = "Inward"
warehouse = row.warehouse
elif table_name == "packed_items":
qty = row.qty
warehouse = row.warehouse
type_of_transaction = "Outward"
if self.is_return:
type_of_transaction = "Inward"
else:
qty = row.stock_qty if self.doctype != "Stock Entry" else row.transfer_qty
type_of_transaction = get_type_of_transaction(self, row)
Expand Down
7 changes: 6 additions & 1 deletion erpnext/stock/doctype/delivery_note/delivery_note.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,12 @@ def on_submit(self):
elif self.issue_credit_note:
self.make_return_invoice()

self.make_bundle_using_old_serial_batch_fields()
for table_name in ["items", "packed_items"]:
if not self.get(table_name):
continue

self.make_bundle_using_old_serial_batch_fields(table_name)

# Updating stock ledger should always be called after updating prevdoc status,
# because updating reserved qty in bin depends upon updated delivered qty in SO
self.update_stock_ledger()
Expand Down
51 changes: 51 additions & 0 deletions erpnext/stock/doctype/delivery_note/test_delivery_note.py
Original file line number Diff line number Diff line change
Expand Up @@ -1078,6 +1078,8 @@ def test_make_sales_invoice_from_dn_with_returned_qty_duplicate_items(self):
self.assertEqual(si2.items[1].qty, 1)

def test_delivery_note_bundle_with_batched_item(self):
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)

batched_bundle = make_item("_Test Batched bundle", {"is_stock_item": 0})
batched_item = make_item(
"_Test Batched Item",
Expand All @@ -1099,6 +1101,8 @@ def test_delivery_note_bundle_with_batched_item(self):
batch_no = get_batch_from_bundle(dn.packed_items[0].serial_and_batch_bundle)
self.assertTrue(batch_no)

frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)

def test_payment_terms_are_fetched_when_creating_sales_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
create_payment_terms_template,
Expand Down Expand Up @@ -1551,6 +1555,53 @@ def test_internal_transfer_for_non_stock_item(self):
self.assertEqual(so.items[0].rate, rate)
self.assertEqual(dn.items[0].rate, so.items[0].rate)

def test_use_serial_batch_fields_for_packed_items(self):
bundle_item = make_item("Test _Packed Product Bundle Item ", {"is_stock_item": 0})
serial_item = make_item(
"Test _Packed Serial Item ",
{"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "SN-TESTSERIAL-.#####"},
)
batch_item = make_item(
"Test _Packed Batch Item ",
{
"is_stock_item": 1,
"has_batch_no": 1,
"batch_no_series": "BATCH-TESTSERIAL-.#####",
"create_new_batch": 1,
},
)
make_product_bundle(parent=bundle_item.name, items=[serial_item.name, batch_item.name])

item_details = {}
for item in [serial_item, batch_item]:
se = make_stock_entry(
item_code=item.name, target="_Test Warehouse - _TC", qty=5, basic_rate=100
)
item_details[item.name] = se.items[0].serial_and_batch_bundle

dn = create_delivery_note(item_code=bundle_item.name, qty=1, do_not_submit=True)
serial_no = ""
for row in dn.packed_items:
row.use_serial_batch_fields = 1

if row.item_code == serial_item.name:
serial_and_batch_bundle = item_details[serial_item.name]
row.serial_no = get_serial_nos_from_bundle(serial_and_batch_bundle)[3]
serial_no = row.serial_no
else:
serial_and_batch_bundle = item_details[batch_item.name]
row.batch_no = get_batch_from_bundle(serial_and_batch_bundle)

dn.submit()
dn.load_from_db()

for row in dn.packed_items:
self.assertTrue(row.serial_no or row.batch_no)
self.assertTrue(row.serial_and_batch_bundle)

if row.serial_no:
self.assertEqual(row.serial_no, serial_no)


def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")
Expand Down
3 changes: 3 additions & 0 deletions erpnext/stock/doctype/packed_item/packed_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,9 @@ def update_packed_item_stock_data(main_item_row, pi_row, packing_item, item_data
bin = get_packed_item_bin_qty(packing_item.item_code, pi_row.warehouse)
pi_row.actual_qty = flt(bin.get("actual_qty"))
pi_row.projected_qty = flt(bin.get("projected_qty"))
pi_row.use_serial_batch_fields = frappe.db.get_single_value(
"Stock Settings", "use_serial_batch_fields"
)


def update_packed_item_price_data(pi_row, item_data, doc):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,9 @@ def set_incoming_rate_for_outward_transaction(
if sn_obj.batch_avg_rate.get(d.batch_no):
d.incoming_rate = abs(sn_obj.batch_avg_rate.get(d.batch_no))

available_qty = flt(sn_obj.available_qty.get(d.batch_no))
available_qty = flt(sn_obj.available_qty.get(d.batch_no), d.precision("qty"))
if self.docstatus == 1:
available_qty += flt(d.qty)
available_qty += flt(d.qty, d.precision("qty"))

if not allow_negative_stock:
self.validate_negative_batch(d.batch_no, available_qty)
Expand Down
2 changes: 1 addition & 1 deletion erpnext/stock/doctype/stock_entry/stock_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -1033,7 +1033,7 @@ def make_serial_and_batch_bundle_for_outward(self):
already_picked_serial_nos = []

for row in self.items:
if row.use_serial_batch_fields and (row.serial_no or row.batch_no):
if row.use_serial_batch_fields:
continue

if not row.s_warehouse:
Expand Down
Loading