fix: serial and batch for internal transfer (#40467)
* fix: serial and batch for internal transfer
* chore: fix test cases
(cherry picked from commit 59222813af)
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
This commit is contained in:
@@ -3932,7 +3932,6 @@ def create_internal_supplier(supplier_name, represents_company, allowed_to_inter
|
|||||||
)
|
)
|
||||||
|
|
||||||
supplier.append("companies", {"company": allowed_to_interact_with})
|
supplier.append("companies", {"company": allowed_to_interact_with})
|
||||||
|
|
||||||
supplier.insert()
|
supplier.insert()
|
||||||
supplier_name = supplier.name
|
supplier_name = supplier.name
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -513,6 +513,14 @@ class BuyingController(SubcontractingController):
|
|||||||
(not cint(self.is_return) and self.docstatus == 1)
|
(not cint(self.is_return) and self.docstatus == 1)
|
||||||
or (cint(self.is_return) and self.docstatus == 2)
|
or (cint(self.is_return) and self.docstatus == 2)
|
||||||
):
|
):
|
||||||
|
serial_and_batch_bundle = d.get("serial_and_batch_bundle")
|
||||||
|
if self.is_internal_transfer() and self.is_return and self.docstatus == 2:
|
||||||
|
serial_and_batch_bundle = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_detail_no": d.name, "warehouse": d.from_warehouse},
|
||||||
|
"serial_and_batch_bundle",
|
||||||
|
)
|
||||||
|
|
||||||
from_warehouse_sle = self.get_sl_entries(
|
from_warehouse_sle = self.get_sl_entries(
|
||||||
d,
|
d,
|
||||||
{
|
{
|
||||||
@@ -521,19 +529,24 @@ class BuyingController(SubcontractingController):
|
|||||||
"outgoing_rate": d.rate,
|
"outgoing_rate": d.rate,
|
||||||
"recalculate_rate": 1,
|
"recalculate_rate": 1,
|
||||||
"dependant_sle_voucher_detail_no": d.name,
|
"dependant_sle_voucher_detail_no": d.name,
|
||||||
|
"serial_and_batch_bundle": serial_and_batch_bundle,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
sl_entries.append(from_warehouse_sle)
|
sl_entries.append(from_warehouse_sle)
|
||||||
|
|
||||||
|
type_of_transaction = "Inward"
|
||||||
|
if self.docstatus == 2:
|
||||||
|
type_of_transaction = "Outward"
|
||||||
|
|
||||||
sle = self.get_sl_entries(
|
sle = self.get_sl_entries(
|
||||||
d,
|
d,
|
||||||
{
|
{
|
||||||
"actual_qty": flt(pr_qty),
|
"actual_qty": flt(pr_qty),
|
||||||
"serial_and_batch_bundle": (
|
"serial_and_batch_bundle": (
|
||||||
d.serial_and_batch_bundle
|
d.serial_and_batch_bundle
|
||||||
if not self.is_internal_transfer()
|
if not self.is_internal_transfer() or self.is_return
|
||||||
else self.get_package_for_target_warehouse(d)
|
else self.get_package_for_target_warehouse(d, type_of_transaction=type_of_transaction)
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -570,7 +583,17 @@ class BuyingController(SubcontractingController):
|
|||||||
or (cint(self.is_return) and self.docstatus == 1)
|
or (cint(self.is_return) and self.docstatus == 1)
|
||||||
):
|
):
|
||||||
from_warehouse_sle = self.get_sl_entries(
|
from_warehouse_sle = self.get_sl_entries(
|
||||||
d, {"actual_qty": -1 * pr_qty, "warehouse": d.from_warehouse, "recalculate_rate": 1}
|
d,
|
||||||
|
{
|
||||||
|
"actual_qty": -1 * pr_qty,
|
||||||
|
"warehouse": d.from_warehouse,
|
||||||
|
"recalculate_rate": 1,
|
||||||
|
"serial_and_batch_bundle": (
|
||||||
|
self.get_package_for_target_warehouse(d, d.from_warehouse, "Inward")
|
||||||
|
if self.is_internal_transfer() and self.is_return
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
sl_entries.append(from_warehouse_sle)
|
sl_entries.append(from_warehouse_sle)
|
||||||
@@ -597,13 +620,15 @@ class BuyingController(SubcontractingController):
|
|||||||
via_landed_cost_voucher=via_landed_cost_voucher,
|
via_landed_cost_voucher=via_landed_cost_voucher,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_package_for_target_warehouse(self, item) -> str:
|
def get_package_for_target_warehouse(self, item, warehouse=None, type_of_transaction=None) -> str:
|
||||||
if not item.serial_and_batch_bundle:
|
if not item.serial_and_batch_bundle:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
if not warehouse:
|
||||||
|
warehouse = item.warehouse
|
||||||
|
|
||||||
return self.make_package_for_transfer(
|
return self.make_package_for_transfer(
|
||||||
item.serial_and_batch_bundle,
|
item.serial_and_batch_bundle, warehouse, type_of_transaction=type_of_transaction
|
||||||
item.warehouse,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def update_ordered_and_reserved_qty(self):
|
def update_ordered_and_reserved_qty(self):
|
||||||
|
|||||||
@@ -423,6 +423,15 @@ def make_return_doc(
|
|||||||
]:
|
]:
|
||||||
type_of_transaction = "Outward"
|
type_of_transaction = "Outward"
|
||||||
|
|
||||||
|
warehouse = source_doc.warehouse if qty_field == "stock_qty" else source_doc.rejected_warehouse
|
||||||
|
if source_parent.doctype in [
|
||||||
|
"Sales Invoice",
|
||||||
|
"POS Invoice",
|
||||||
|
"Delivery Note",
|
||||||
|
] and source_parent.get("is_internal_customer"):
|
||||||
|
type_of_transaction = "Outward"
|
||||||
|
warehouse = source_doc.target_warehouse
|
||||||
|
|
||||||
cls_obj = SerialBatchCreation(
|
cls_obj = SerialBatchCreation(
|
||||||
{
|
{
|
||||||
"type_of_transaction": type_of_transaction,
|
"type_of_transaction": type_of_transaction,
|
||||||
@@ -432,7 +441,7 @@ def make_return_doc(
|
|||||||
"returned_serial_nos": returned_serial_nos,
|
"returned_serial_nos": returned_serial_nos,
|
||||||
"voucher_type": source_parent.doctype,
|
"voucher_type": source_parent.doctype,
|
||||||
"do_not_submit": True,
|
"do_not_submit": True,
|
||||||
"warehouse": source_doc.warehouse,
|
"warehouse": warehouse,
|
||||||
"has_serial_no": item_details.has_serial_no,
|
"has_serial_no": item_details.has_serial_no,
|
||||||
"has_batch_no": item_details.has_batch_no,
|
"has_batch_no": item_details.has_batch_no,
|
||||||
}
|
}
|
||||||
@@ -575,11 +584,14 @@ def make_return_doc(
|
|||||||
if not item_details.has_batch_no and not item_details.has_serial_no:
|
if not item_details.has_batch_no and not item_details.has_serial_no:
|
||||||
return
|
return
|
||||||
|
|
||||||
for qty_field in ["stock_qty", "rejected_qty"]:
|
if not target_doc.get("use_serial_batch_fields"):
|
||||||
if target_doc.get(qty_field) and not target_doc.get("use_serial_batch_fields"):
|
for qty_field in ["stock_qty", "rejected_qty"]:
|
||||||
|
if not target_doc.get(qty_field):
|
||||||
|
continue
|
||||||
|
|
||||||
update_serial_batch_no(source_doc, target_doc, source_parent, item_details, qty_field)
|
update_serial_batch_no(source_doc, target_doc, source_parent, item_details, qty_field)
|
||||||
elif target_doc.get(qty_field) and target_doc.get("use_serial_batch_fields"):
|
elif target_doc.get("use_serial_batch_fields"):
|
||||||
update_non_bundled_serial_nos(source_doc, target_doc, source_parent)
|
update_non_bundled_serial_nos(source_doc, target_doc, source_parent)
|
||||||
|
|
||||||
def update_non_bundled_serial_nos(source_doc, target_doc, source_parent):
|
def update_non_bundled_serial_nos(source_doc, target_doc, source_parent):
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|||||||
@@ -442,8 +442,10 @@ class SellingController(StockController):
|
|||||||
# Get incoming rate based on original item cost based on valuation method
|
# Get incoming rate based on original item cost based on valuation method
|
||||||
qty = flt(d.get("stock_qty") or d.get("actual_qty"))
|
qty = flt(d.get("stock_qty") or d.get("actual_qty"))
|
||||||
|
|
||||||
if not d.incoming_rate or (
|
if (
|
||||||
get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return")
|
not d.incoming_rate
|
||||||
|
or self.is_internal_transfer()
|
||||||
|
or (get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return"))
|
||||||
):
|
):
|
||||||
d.incoming_rate = get_incoming_rate(
|
d.incoming_rate = get_incoming_rate(
|
||||||
{
|
{
|
||||||
@@ -458,6 +460,8 @@ class SellingController(StockController):
|
|||||||
"voucher_no": self.name,
|
"voucher_no": self.name,
|
||||||
"voucher_detail_no": d.name,
|
"voucher_detail_no": d.name,
|
||||||
"allow_zero_valuation": d.get("allow_zero_valuation"),
|
"allow_zero_valuation": d.get("allow_zero_valuation"),
|
||||||
|
"batch_no": d.batch_no,
|
||||||
|
"serial_no": d.serial_no,
|
||||||
},
|
},
|
||||||
raise_error_if_no_rate=False,
|
raise_error_if_no_rate=False,
|
||||||
)
|
)
|
||||||
@@ -530,13 +534,26 @@ class SellingController(StockController):
|
|||||||
self.make_sl_entries(sl_entries)
|
self.make_sl_entries(sl_entries)
|
||||||
|
|
||||||
def get_sle_for_source_warehouse(self, item_row):
|
def get_sle_for_source_warehouse(self, item_row):
|
||||||
|
serial_and_batch_bundle = item_row.serial_and_batch_bundle
|
||||||
|
if serial_and_batch_bundle and self.is_internal_transfer() and self.is_return:
|
||||||
|
if self.docstatus == 1:
|
||||||
|
serial_and_batch_bundle = self.make_package_for_transfer(
|
||||||
|
serial_and_batch_bundle, item_row.warehouse, type_of_transaction="Inward"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
serial_and_batch_bundle = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_detail_no": item_row.name, "warehouse": item_row.warehouse},
|
||||||
|
"serial_and_batch_bundle",
|
||||||
|
)
|
||||||
|
|
||||||
sle = self.get_sl_entries(
|
sle = self.get_sl_entries(
|
||||||
item_row,
|
item_row,
|
||||||
{
|
{
|
||||||
"actual_qty": -1 * flt(item_row.qty),
|
"actual_qty": -1 * flt(item_row.qty),
|
||||||
"incoming_rate": item_row.incoming_rate,
|
"incoming_rate": item_row.incoming_rate,
|
||||||
"recalculate_rate": cint(self.is_return),
|
"recalculate_rate": cint(self.is_return),
|
||||||
"serial_and_batch_bundle": item_row.serial_and_batch_bundle,
|
"serial_and_batch_bundle": serial_and_batch_bundle,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if item_row.target_warehouse and not cint(self.is_return):
|
if item_row.target_warehouse and not cint(self.is_return):
|
||||||
@@ -557,9 +574,15 @@ class SellingController(StockController):
|
|||||||
if item_row.warehouse:
|
if item_row.warehouse:
|
||||||
sle.dependant_sle_voucher_detail_no = item_row.name
|
sle.dependant_sle_voucher_detail_no = item_row.name
|
||||||
|
|
||||||
if item_row.serial_and_batch_bundle:
|
if item_row.serial_and_batch_bundle and not cint(self.is_return):
|
||||||
|
type_of_transaction = "Inward"
|
||||||
|
if cint(self.is_return):
|
||||||
|
type_of_transaction = "Outward"
|
||||||
|
|
||||||
sle["serial_and_batch_bundle"] = self.make_package_for_transfer(
|
sle["serial_and_batch_bundle"] = self.make_package_for_transfer(
|
||||||
item_row.serial_and_batch_bundle, item_row.target_warehouse
|
item_row.serial_and_batch_bundle,
|
||||||
|
item_row.target_warehouse,
|
||||||
|
type_of_transaction=type_of_transaction,
|
||||||
)
|
)
|
||||||
|
|
||||||
return sle
|
return sle
|
||||||
|
|||||||
@@ -236,6 +236,14 @@ class StockController(AccountsController):
|
|||||||
qty = row.get("rejected_qty")
|
qty = row.get("rejected_qty")
|
||||||
warehouse = row.get("rejected_warehouse")
|
warehouse = row.get("rejected_warehouse")
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.is_internal_transfer()
|
||||||
|
and self.doctype in ["Sales Invoice", "Delivery Note"]
|
||||||
|
and self.is_return
|
||||||
|
):
|
||||||
|
warehouse = row.get("target_warehouse") or row.get("warehouse")
|
||||||
|
type_of_transaction = "Outward"
|
||||||
|
|
||||||
bundle_details.update(
|
bundle_details.update(
|
||||||
{
|
{
|
||||||
"qty": qty,
|
"qty": qty,
|
||||||
@@ -579,7 +587,7 @@ class StockController(AccountsController):
|
|||||||
bundle_doc.warehouse = warehouse
|
bundle_doc.warehouse = warehouse
|
||||||
bundle_doc.type_of_transaction = type_of_transaction
|
bundle_doc.type_of_transaction = type_of_transaction
|
||||||
bundle_doc.voucher_type = self.doctype
|
bundle_doc.voucher_type = self.doctype
|
||||||
bundle_doc.voucher_no = self.name
|
bundle_doc.voucher_no = "" if self.is_new() or self.docstatus == 2 else self.name
|
||||||
bundle_doc.is_cancelled = 0
|
bundle_doc.is_cancelled = 0
|
||||||
|
|
||||||
for row in bundle_doc.entries:
|
for row in bundle_doc.entries:
|
||||||
@@ -595,6 +603,7 @@ class StockController(AccountsController):
|
|||||||
|
|
||||||
bundle_doc.calculate_qty_and_amount()
|
bundle_doc.calculate_qty_and_amount()
|
||||||
bundle_doc.flags.ignore_permissions = True
|
bundle_doc.flags.ignore_permissions = True
|
||||||
|
bundle_doc.flags.ignore_validate = True
|
||||||
bundle_doc.save(ignore_permissions=True)
|
bundle_doc.save(ignore_permissions=True)
|
||||||
|
|
||||||
return bundle_doc.name
|
return bundle_doc.name
|
||||||
|
|||||||
@@ -542,6 +542,10 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
|||||||
frappe.throw(__("Please add atleast one Serial No / Batch No"));
|
frappe.throw(__("Please add atleast one Serial No / Batch No"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!warehouse) {
|
||||||
|
frappe.throw(__("Please select a Warehouse"));
|
||||||
|
}
|
||||||
|
|
||||||
frappe
|
frappe
|
||||||
.call({
|
.call({
|
||||||
method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.add_serial_batch_ledgers",
|
method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.add_serial_batch_ledgers",
|
||||||
|
|||||||
@@ -333,6 +333,7 @@ class DeliveryNote(SellingController):
|
|||||||
"type_of_transaction": "Outward",
|
"type_of_transaction": "Outward",
|
||||||
"serial_and_batch_bundle": bundle_id,
|
"serial_and_batch_bundle": bundle_id,
|
||||||
"item_code": item.get("item_code"),
|
"item_code": item.get("item_code"),
|
||||||
|
"warehouse": item.get("warehouse"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2522,6 +2522,280 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
self.assertEqual(row.serial_no, "\n".join(serial_nos[:2]))
|
self.assertEqual(row.serial_no, "\n".join(serial_nos[:2]))
|
||||||
self.assertEqual(row.rejected_serial_no, serial_nos[2])
|
self.assertEqual(row.rejected_serial_no, serial_nos[2])
|
||||||
|
|
||||||
|
def test_internal_transfer_with_serial_batch_items_and_their_valuation(self):
|
||||||
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
|
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
|
||||||
|
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||||
|
|
||||||
|
prepare_data_for_internal_transfer()
|
||||||
|
|
||||||
|
customer = "_Test Internal Customer 2"
|
||||||
|
company = "_Test Company with perpetual inventory"
|
||||||
|
|
||||||
|
batch_item_doc = make_item(
|
||||||
|
"_Test Batch Item For Stock Transfer",
|
||||||
|
{"has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "BT-BIFST-.####"},
|
||||||
|
)
|
||||||
|
|
||||||
|
serial_item_doc = make_item(
|
||||||
|
"_Test Serial No Item For Stock Transfer",
|
||||||
|
{"has_serial_no": 1, "serial_no_series": "BT-BIFST-.####"},
|
||||||
|
)
|
||||||
|
|
||||||
|
inward_entry = make_purchase_receipt(
|
||||||
|
item_code=batch_item_doc.name,
|
||||||
|
qty=10,
|
||||||
|
rate=150,
|
||||||
|
warehouse="Stores - TCP1",
|
||||||
|
company="_Test Company with perpetual inventory",
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
do_not_submit=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
inward_entry.append(
|
||||||
|
"items",
|
||||||
|
{
|
||||||
|
"item_code": serial_item_doc.name,
|
||||||
|
"qty": 15,
|
||||||
|
"rate": 250,
|
||||||
|
"item_name": serial_item_doc.item_name,
|
||||||
|
"conversion_factor": 1.0,
|
||||||
|
"uom": serial_item_doc.stock_uom,
|
||||||
|
"stock_uom": serial_item_doc.stock_uom,
|
||||||
|
"warehouse": "Stores - TCP1",
|
||||||
|
"use_serial_batch_fields": 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
inward_entry.submit()
|
||||||
|
inward_entry.reload()
|
||||||
|
|
||||||
|
for row in inward_entry.items:
|
||||||
|
self.assertTrue(row.serial_and_batch_bundle)
|
||||||
|
|
||||||
|
inter_transfer_dn = create_delivery_note(
|
||||||
|
item_code=inward_entry.items[0].item_code,
|
||||||
|
company=company,
|
||||||
|
customer=customer,
|
||||||
|
cost_center="Main - TCP1",
|
||||||
|
expense_account="Cost of Goods Sold - TCP1",
|
||||||
|
qty=10,
|
||||||
|
rate=500,
|
||||||
|
warehouse="Stores - TCP1",
|
||||||
|
target_warehouse="Work In Progress - TCP1",
|
||||||
|
batch_no=get_batch_from_bundle(inward_entry.items[0].serial_and_batch_bundle),
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
do_not_submit=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
inter_transfer_dn.append(
|
||||||
|
"items",
|
||||||
|
{
|
||||||
|
"item_code": serial_item_doc.name,
|
||||||
|
"qty": 15,
|
||||||
|
"rate": 350,
|
||||||
|
"item_name": serial_item_doc.item_name,
|
||||||
|
"conversion_factor": 1.0,
|
||||||
|
"uom": serial_item_doc.stock_uom,
|
||||||
|
"stock_uom": serial_item_doc.stock_uom,
|
||||||
|
"warehouse": "Stores - TCP1",
|
||||||
|
"target_warehouse": "Work In Progress - TCP1",
|
||||||
|
"serial_no": "\n".join(
|
||||||
|
get_serial_nos_from_bundle(inward_entry.items[1].serial_and_batch_bundle)
|
||||||
|
),
|
||||||
|
"use_serial_batch_fields": 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
inter_transfer_dn.submit()
|
||||||
|
inter_transfer_dn.reload()
|
||||||
|
for row in inter_transfer_dn.items:
|
||||||
|
if row.item_code == batch_item_doc.name:
|
||||||
|
self.assertEqual(row.rate, 150.0)
|
||||||
|
else:
|
||||||
|
self.assertEqual(row.rate, 250.0)
|
||||||
|
|
||||||
|
self.assertTrue(row.serial_and_batch_bundle)
|
||||||
|
|
||||||
|
inter_transfer_pr = make_inter_company_purchase_receipt(inter_transfer_dn.name)
|
||||||
|
for row in inter_transfer_pr.items:
|
||||||
|
row.from_warehouse = "Work In Progress - TCP1"
|
||||||
|
row.warehouse = "Stores - TCP1"
|
||||||
|
inter_transfer_pr.submit()
|
||||||
|
|
||||||
|
for row in inter_transfer_pr.items:
|
||||||
|
if row.item_code == batch_item_doc.name:
|
||||||
|
self.assertEqual(row.rate, 150.0)
|
||||||
|
else:
|
||||||
|
self.assertEqual(row.rate, 250.0)
|
||||||
|
|
||||||
|
self.assertTrue(row.serial_and_batch_bundle)
|
||||||
|
|
||||||
|
inter_transfer_pr_return = make_return_doc("Purchase Receipt", inter_transfer_pr.name)
|
||||||
|
|
||||||
|
inter_transfer_pr_return.submit()
|
||||||
|
inter_transfer_pr_return.reload()
|
||||||
|
for row in inter_transfer_pr_return.items:
|
||||||
|
self.assertTrue(row.serial_and_batch_bundle)
|
||||||
|
if row.item_code == serial_item_doc.name:
|
||||||
|
self.assertEqual(row.rate, 250.0)
|
||||||
|
serial_nos = get_serial_nos_from_bundle(row.serial_and_batch_bundle)
|
||||||
|
for sn in serial_nos:
|
||||||
|
serial_no_details = frappe.db.get_value("Serial No", sn, ["status", "warehouse"], as_dict=1)
|
||||||
|
self.assertTrue(serial_no_details.status == "Active")
|
||||||
|
self.assertEqual(serial_no_details.warehouse, "Work In Progress - TCP1")
|
||||||
|
|
||||||
|
inter_transfer_dn_return = make_return_doc("Delivery Note", inter_transfer_dn.name)
|
||||||
|
inter_transfer_dn_return.posting_date = today()
|
||||||
|
inter_transfer_dn_return.posting_time = nowtime()
|
||||||
|
for row in inter_transfer_dn_return.items:
|
||||||
|
row.target_warehouse = "Work In Progress - TCP1"
|
||||||
|
|
||||||
|
inter_transfer_dn_return.submit()
|
||||||
|
inter_transfer_dn_return.reload()
|
||||||
|
|
||||||
|
for row in inter_transfer_dn_return.items:
|
||||||
|
self.assertTrue(row.serial_and_batch_bundle)
|
||||||
|
|
||||||
|
def test_internal_transfer_with_serial_batch_items_without_user_serial_batch_fields(self):
|
||||||
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
|
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
|
||||||
|
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
|
||||||
|
|
||||||
|
prepare_data_for_internal_transfer()
|
||||||
|
|
||||||
|
customer = "_Test Internal Customer 2"
|
||||||
|
company = "_Test Company with perpetual inventory"
|
||||||
|
|
||||||
|
batch_item_doc = make_item(
|
||||||
|
"_Test Batch Item For Stock Transfer USE SERIAL BATCH FIELDS",
|
||||||
|
{"has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "USBF-BT-BIFST-.####"},
|
||||||
|
)
|
||||||
|
|
||||||
|
serial_item_doc = make_item(
|
||||||
|
"_Test Serial No Item For Stock Transfer USE SERIAL BATCH FIELDS",
|
||||||
|
{"has_serial_no": 1, "serial_no_series": "USBF-BT-BIFST-.####"},
|
||||||
|
)
|
||||||
|
|
||||||
|
inward_entry = make_purchase_receipt(
|
||||||
|
item_code=batch_item_doc.name,
|
||||||
|
qty=10,
|
||||||
|
rate=150,
|
||||||
|
warehouse="Stores - TCP1",
|
||||||
|
company="_Test Company with perpetual inventory",
|
||||||
|
use_serial_batch_fields=0,
|
||||||
|
do_not_submit=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
inward_entry.append(
|
||||||
|
"items",
|
||||||
|
{
|
||||||
|
"item_code": serial_item_doc.name,
|
||||||
|
"qty": 15,
|
||||||
|
"rate": 250,
|
||||||
|
"item_name": serial_item_doc.item_name,
|
||||||
|
"conversion_factor": 1.0,
|
||||||
|
"uom": serial_item_doc.stock_uom,
|
||||||
|
"stock_uom": serial_item_doc.stock_uom,
|
||||||
|
"warehouse": "Stores - TCP1",
|
||||||
|
"use_serial_batch_fields": 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
inward_entry.submit()
|
||||||
|
inward_entry.reload()
|
||||||
|
|
||||||
|
for row in inward_entry.items:
|
||||||
|
self.assertTrue(row.serial_and_batch_bundle)
|
||||||
|
|
||||||
|
inter_transfer_dn = create_delivery_note(
|
||||||
|
item_code=inward_entry.items[0].item_code,
|
||||||
|
company=company,
|
||||||
|
customer=customer,
|
||||||
|
cost_center="Main - TCP1",
|
||||||
|
expense_account="Cost of Goods Sold - TCP1",
|
||||||
|
qty=10,
|
||||||
|
rate=500,
|
||||||
|
warehouse="Stores - TCP1",
|
||||||
|
target_warehouse="Work In Progress - TCP1",
|
||||||
|
batch_no=get_batch_from_bundle(inward_entry.items[0].serial_and_batch_bundle),
|
||||||
|
use_serial_batch_fields=0,
|
||||||
|
do_not_submit=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
inter_transfer_dn.append(
|
||||||
|
"items",
|
||||||
|
{
|
||||||
|
"item_code": serial_item_doc.name,
|
||||||
|
"qty": 15,
|
||||||
|
"rate": 350,
|
||||||
|
"item_name": serial_item_doc.item_name,
|
||||||
|
"conversion_factor": 1.0,
|
||||||
|
"uom": serial_item_doc.stock_uom,
|
||||||
|
"stock_uom": serial_item_doc.stock_uom,
|
||||||
|
"warehouse": "Stores - TCP1",
|
||||||
|
"target_warehouse": "Work In Progress - TCP1",
|
||||||
|
"serial_no": "\n".join(
|
||||||
|
get_serial_nos_from_bundle(inward_entry.items[1].serial_and_batch_bundle)
|
||||||
|
),
|
||||||
|
"use_serial_batch_fields": 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
inter_transfer_dn.submit()
|
||||||
|
inter_transfer_dn.reload()
|
||||||
|
for row in inter_transfer_dn.items:
|
||||||
|
if row.item_code == batch_item_doc.name:
|
||||||
|
self.assertEqual(row.rate, 150.0)
|
||||||
|
else:
|
||||||
|
self.assertEqual(row.rate, 250.0)
|
||||||
|
|
||||||
|
self.assertTrue(row.serial_and_batch_bundle)
|
||||||
|
|
||||||
|
inter_transfer_pr = make_inter_company_purchase_receipt(inter_transfer_dn.name)
|
||||||
|
for row in inter_transfer_pr.items:
|
||||||
|
row.from_warehouse = "Work In Progress - TCP1"
|
||||||
|
row.warehouse = "Stores - TCP1"
|
||||||
|
inter_transfer_pr.submit()
|
||||||
|
|
||||||
|
for row in inter_transfer_pr.items:
|
||||||
|
if row.item_code == batch_item_doc.name:
|
||||||
|
self.assertEqual(row.rate, 150.0)
|
||||||
|
else:
|
||||||
|
self.assertEqual(row.rate, 250.0)
|
||||||
|
|
||||||
|
self.assertTrue(row.serial_and_batch_bundle)
|
||||||
|
|
||||||
|
inter_transfer_pr_return = make_return_doc("Purchase Receipt", inter_transfer_pr.name)
|
||||||
|
|
||||||
|
inter_transfer_pr_return.submit()
|
||||||
|
inter_transfer_pr_return.reload()
|
||||||
|
for row in inter_transfer_pr_return.items:
|
||||||
|
self.assertTrue(row.serial_and_batch_bundle)
|
||||||
|
if row.item_code == serial_item_doc.name:
|
||||||
|
self.assertEqual(row.rate, 250.0)
|
||||||
|
serial_nos = get_serial_nos_from_bundle(row.serial_and_batch_bundle)
|
||||||
|
for sn in serial_nos:
|
||||||
|
serial_no_details = frappe.db.get_value("Serial No", sn, ["status", "warehouse"], as_dict=1)
|
||||||
|
self.assertTrue(serial_no_details.status == "Active")
|
||||||
|
self.assertEqual(serial_no_details.warehouse, "Work In Progress - TCP1")
|
||||||
|
|
||||||
|
inter_transfer_dn_return = make_return_doc("Delivery Note", inter_transfer_dn.name)
|
||||||
|
inter_transfer_dn_return.posting_date = today()
|
||||||
|
inter_transfer_dn_return.posting_time = nowtime()
|
||||||
|
for row in inter_transfer_dn_return.items:
|
||||||
|
row.target_warehouse = "Work In Progress - TCP1"
|
||||||
|
|
||||||
|
inter_transfer_dn_return.submit()
|
||||||
|
inter_transfer_dn_return.reload()
|
||||||
|
|
||||||
|
for row in inter_transfer_dn_return.items:
|
||||||
|
self.assertTrue(row.serial_and_batch_bundle)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -113,6 +113,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "voucher_no",
|
"fieldname": "voucher_no",
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "Dynamic Link",
|
||||||
|
"in_standard_filter": 1,
|
||||||
"label": "Voucher No",
|
"label": "Voucher No",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "voucher_type",
|
"options": "voucher_type",
|
||||||
@@ -250,7 +251,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-12-07 17:56:55.528563",
|
"modified": "2024-03-15 15:22:24.003486",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Serial and Batch Bundle",
|
"name": "Serial and Batch Bundle",
|
||||||
|
|||||||
@@ -801,6 +801,7 @@ class SerialandBatchBundle(Document):
|
|||||||
self.set_purchase_document_no()
|
self.set_purchase_document_no()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
|
self.validate_batch_inventory()
|
||||||
self.validate_serial_nos_inventory()
|
self.validate_serial_nos_inventory()
|
||||||
|
|
||||||
def set_purchase_document_no(self):
|
def set_purchase_document_no(self):
|
||||||
|
|||||||
@@ -2606,6 +2606,7 @@ def move_sample_to_retention_warehouse(company, items):
|
|||||||
"type_of_transaction": "Outward",
|
"type_of_transaction": "Outward",
|
||||||
"serial_and_batch_bundle": item.get("serial_and_batch_bundle"),
|
"serial_and_batch_bundle": item.get("serial_and_batch_bundle"),
|
||||||
"item_code": item.get("item_code"),
|
"item_code": item.get("item_code"),
|
||||||
|
"warehouse": item.get("t_warehouse"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -999,6 +999,7 @@ class TestStockEntry(FrappeTestCase):
|
|||||||
"type_of_transaction": "Inward",
|
"type_of_transaction": "Inward",
|
||||||
"serial_and_batch_bundle": s2.items[0].serial_and_batch_bundle,
|
"serial_and_batch_bundle": s2.items[0].serial_and_batch_bundle,
|
||||||
"item_code": "_Test Serialized Item",
|
"item_code": "_Test Serialized Item",
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -820,6 +820,10 @@ class SerialBatchCreation:
|
|||||||
self.remove_returned_serial_nos(new_package)
|
self.remove_returned_serial_nos(new_package)
|
||||||
|
|
||||||
new_package.docstatus = 0
|
new_package.docstatus = 0
|
||||||
|
new_package.warehouse = self.warehouse
|
||||||
|
new_package.voucher_no = ""
|
||||||
|
new_package.posting_date = today()
|
||||||
|
new_package.posting_time = nowtime()
|
||||||
new_package.type_of_transaction = self.type_of_transaction
|
new_package.type_of_transaction = self.type_of_transaction
|
||||||
new_package.returned_against = self.get("returned_against")
|
new_package.returned_against = self.get("returned_against")
|
||||||
new_package.save()
|
new_package.save()
|
||||||
|
|||||||
Reference in New Issue
Block a user