From 5de9b6ac757246c6578af32f521398ca1629d51d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 12 Apr 2024 11:25:34 +0530 Subject: [PATCH] fix: do not validate batch qty for LCV (cherry picked from commit baf0c83cc5c98bf74a6a437f4bec2131074a9ae0) --- erpnext/controllers/stock_controller.py | 15 +- .../landed_cost_voucher.py | 2 +- .../test_landed_cost_voucher.py | 201 ++++++++++++++++++ .../serial_and_batch_bundle.py | 6 + 4 files changed, 220 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 73436508282..219a1d68c52 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -1119,7 +1119,7 @@ class StockController(AccountsController): message += _("Please adjust the qty or edit {0} to proceed.").format(rule_link) return message - def repost_future_sle_and_gle(self, force=False): + def repost_future_sle_and_gle(self, force=False, via_landed_cost_voucher=False): args = frappe._dict( { "posting_date": self.posting_date, @@ -1127,6 +1127,7 @@ class StockController(AccountsController): "voucher_type": self.doctype, "voucher_no": self.name, "company": self.company, + "via_landed_cost_voucher": via_landed_cost_voucher, } ) @@ -1138,7 +1139,11 @@ class StockController(AccountsController): frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting") ) if item_based_reposting: - create_item_wise_repost_entries(voucher_type=self.doctype, voucher_no=self.name) + create_item_wise_repost_entries( + voucher_type=self.doctype, + voucher_no=self.name, + via_landed_cost_voucher=via_landed_cost_voucher, + ) else: create_repost_item_valuation_entry(args) @@ -1510,11 +1515,14 @@ def create_repost_item_valuation_entry(args): repost_entry.allow_zero_rate = args.allow_zero_rate repost_entry.flags.ignore_links = True repost_entry.flags.ignore_permissions = True + repost_entry.via_landed_cost_voucher = args.via_landed_cost_voucher repost_entry.save() repost_entry.submit() -def create_item_wise_repost_entries(voucher_type, voucher_no, allow_zero_rate=False): +def create_item_wise_repost_entries( + voucher_type, voucher_no, allow_zero_rate=False, via_landed_cost_voucher=False +): """Using a voucher create repost item valuation records for all item-warehouse pairs.""" stock_ledger_entries = get_items_to_be_repost(voucher_type, voucher_no) @@ -1538,6 +1546,7 @@ def create_item_wise_repost_entries(voucher_type, voucher_no, allow_zero_rate=Fa repost_entry.allow_zero_rate = allow_zero_rate repost_entry.flags.ignore_links = True repost_entry.flags.ignore_permissions = True + repost_entry.via_landed_cost_voucher = via_landed_cost_voucher repost_entry.submit() repost_entries.append(repost_entry) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index 601cde6c905..683e946298a 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -253,7 +253,7 @@ class LandedCostVoucher(Document): doc.make_bundle_using_old_serial_batch_fields(via_landed_cost_voucher=True) doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True) doc.make_gl_entries() - doc.repost_future_sle_and_gle() + doc.repost_future_sle_and_gle(via_landed_cost_voucher=True) def validate_asset_qty_and_status(self, receipt_document_type, receipt_document): for item in self.get("items"): diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 32b384def28..13b7f97b7c4 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -745,6 +745,207 @@ class TestLandedCostVoucher(FrappeTestCase): frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"), ) + def test_do_not_validate_landed_cost_voucher_with_serial_batch_for_legacy_pr(self): + from erpnext.stock.doctype.item.test_item import make_item + from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import get_auto_batch_nos + + frappe.flags.ignore_serial_batch_bundle_validation = True + frappe.flags.use_serial_and_batch_fields = True + sn_item = "Test Don't Validate Landed Cost Voucher Serial NO for Legacy PR" + batch_item = "Test Don't Validate Landed Cost Voucher Batch NO for Legacy PR" + sn_item_doc = make_item( + sn_item, + { + "has_serial_no": 1, + "serial_no_series": "SN-TDVLCVSNO-.####", + "is_stock_item": 1, + }, + ) + + batch_item_doc = make_item( + batch_item, + { + "has_batch_no": 1, + "batch_number_series": "BATCH-TDVLCVSNO-.####", + "create_new_batch": 1, + "is_stock_item": 1, + }, + ) + + serial_nos = [ + "SN-TDVLCVSNO-0001", + "SN-TDVLCVSNO-0002", + "SN-TDVLCVSNO-0003", + "SN-TDVLCVSNO-0004", + "SN-TDVLCVSNO-0005", + ] + + for sn in serial_nos: + if not frappe.db.exists("Serial No", sn): + sn_doc = frappe.get_doc( + { + "doctype": "Serial No", + "item_code": sn_item, + "serial_no": sn, + } + ) + sn_doc.insert() + + if not frappe.db.exists("Batch", "BATCH-TDVLCVSNO-0001"): + batch_doc = frappe.get_doc( + { + "doctype": "Batch", + "item": batch_item, + "batch_id": "BATCH-TDVLCVSNO-0001", + } + ) + batch_doc.insert() + + warehouse = "_Test Warehouse - _TC" + company = frappe.db.get_value("Warehouse", warehouse, "company") + + pr = make_purchase_receipt( + company=company, + warehouse=warehouse, + item_code=sn_item, + qty=5, + rate=100, + uom=sn_item_doc.stock_uom, + stock_uom=sn_item_doc.stock_uom, + do_not_submit=True, + ) + + pr.append( + "items", + { + "item_code": batch_item, + "item_name": batch_item, + "description": "Test Batch Item", + "uom": batch_item_doc.stock_uom, + "stock_uom": batch_item_doc.stock_uom, + "qty": 5, + "rate": 100, + "warehouse": warehouse, + }, + ) + + pr.submit() + pr.reload() + + for sn in serial_nos: + sn_doc = frappe.get_doc("Serial No", sn) + sn_doc.db_set( + { + "warehouse": warehouse, + "status": "Active", + } + ) + + batch_doc.db_set( + { + "batch_qty": 5, + } + ) + + for row in pr.items: + if row.item_code == sn_item: + row.db_set("serial_no", ", ".join(serial_nos)) + else: + row.db_set("batch_no", "BATCH-TDVLCVSNO-0001") + + stock_ledger_entries = frappe.get_all("Stock Ledger Entry", filters={"voucher_no": pr.name}) + for sle in stock_ledger_entries: + doc = frappe.get_doc("Stock Ledger Entry", sle.name) + if doc.item_code == sn_item: + doc.db_set("serial_no", ", ".join(serial_nos)) + else: + doc.db_set("batch_no", "BATCH-TDVLCVSNO-0001") + + dn = create_delivery_note( + company=company, + warehouse=warehouse, + item_code=sn_item, + qty=5, + rate=100, + uom=sn_item_doc.stock_uom, + stock_uom=sn_item_doc.stock_uom, + do_not_submit=True, + ) + + dn.append( + "items", + { + "item_code": batch_item, + "item_name": batch_item, + "description": "Test Batch Item", + "uom": batch_item_doc.stock_uom, + "stock_uom": batch_item_doc.stock_uom, + "qty": 5, + "rate": 100, + "warehouse": warehouse, + }, + ) + + dn.submit() + + stock_ledger_entries = frappe.get_all("Stock Ledger Entry", filters={"voucher_no": dn.name}) + for sle in stock_ledger_entries: + doc = frappe.get_doc("Stock Ledger Entry", sle.name) + if doc.item_code == sn_item: + doc.db_set("serial_no", ", ".join(serial_nos)) + else: + doc.db_set("batch_no", "BATCH-TDVLCVSNO-0001") + + available_batches = get_auto_batch_nos( + frappe._dict( + { + "item_code": batch_item, + "warehouse": warehouse, + "batch_no": ["BATCH-TDVLCVSNO-0001"], + "consider_negative_batches": True, + } + ) + )[0] + + self.assertFalse(available_batches.get("qty")) + + frappe.flags.ignore_serial_batch_bundle_validation = False + frappe.flags.use_serial_and_batch_fields = False + + lcv = make_landed_cost_voucher( + company=pr.company, + receipt_document_type="Purchase Receipt", + receipt_document=pr.name, + charges=20, + distribute_charges_based_on="Qty", + do_not_save=True, + ) + + lcv.get_items_from_purchase_receipts() + lcv.save() + lcv.submit() + + pr.reload() + + for row in pr.items: + self.assertEqual(row.valuation_rate, 102) + self.assertTrue(row.serial_and_batch_bundle) + self.assertEqual( + row.valuation_rate, + frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"), + ) + + lcv.cancel() + pr.reload() + + for row in pr.items: + self.assertEqual(row.valuation_rate, 100) + self.assertTrue(row.serial_and_batch_bundle) + self.assertEqual( + row.valuation_rate, + frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"), + ) + def make_landed_cost_voucher(**args): args = frappe._dict(args) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 19aad3f6296..286a220c5dd 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -860,6 +860,12 @@ class SerialandBatchBundle(Document): self.validate_batch_inventory() def validate_batch_inventory(self): + if ( + self.voucher_type in ["Purchase Invoice", "Purchase Receipt"] + and frappe.db.get_value(self.voucher_type, self.voucher_no, "docstatus") == 1 + ): + return + if not self.has_batch_no: return