From e5457f8bb7f7a850ff70098c69a0614c15b33b58 Mon Sep 17 00:00:00 2001 From: Sudharsanan11 Date: Mon, 1 Dec 2025 19:57:44 +0530 Subject: [PATCH] test(pos): add test for product bundle negative stock validation (cherry picked from commit 26121524560057423b344dc54f453e1191e4911a) # Conflicts: # erpnext/accounts/doctype/pos_invoice/pos_invoice.py --- .../doctype/pos_invoice/pos_invoice.py | 5 ++ .../doctype/pos_invoice/test_pos_invoice.py | 78 +++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index da6cb1acc4d..ffa441aa74f 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -21,7 +21,11 @@ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.stock_ledger import is_negative_stock_allowed +<<<<<<< HEAD class PartialPaymentValidationError(frappe.ValidationError): +======= +class ProductBundleStockValidationError(frappe.ValidationError): +>>>>>>> 2612152456 (test(pos): add test for product bundle negative stock validation) pass @@ -389,6 +393,7 @@ class POSInvoice(SalesInvoice): "
".join(error_msgs), ), title=_("Insufficient Stock for Product Bundle Items"), + exc=ProductBundleStockValidationError, ) else: diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 2cc1d5f22bb..b0c16ac27d1 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -964,6 +964,84 @@ class TestPOSInvoice(unittest.TestCase): frappe.db.rollback(save_point="before_test_delivered_serial_no_case") frappe.set_user("Administrator") + def test_bundle_stock_availability_validation(self): + from erpnext.accounts.doctype.pos_invoice.pos_invoice import ProductBundleStockValidationError + from erpnext.accounts.doctype.pos_invoice_merge_log.test_pos_invoice_merge_log import ( + init_user_and_profile, + ) + from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle + from erpnext.stock.doctype.item.test_item import create_item + + init_user_and_profile() + + frappe.set_user("Administrator") + + warehouse = "_Test Warehouse - _TC" + company = "_Test Company" + + # Create stock sub-items + sub_item_a = "_Test Bundle SubA" + if not frappe.db.exists("Item", sub_item_a): + create_item( + item_code=sub_item_a, + is_stock_item=1, + ) + + sub_item_b = "_Test Bundle SubB" + if not frappe.db.exists("Item", sub_item_b): + create_item( + item_code=sub_item_b, + is_stock_item=1, + ) + + # Add initial stock: SubA=5, SubB=2 + make_stock_entry(item_code=sub_item_a, target=warehouse, qty=5, company=company) + make_stock_entry(item_code=sub_item_b, target=warehouse, qty=2, company=company) + + # Create Product Bundle: Test Bundle (SubA x2 + SubB x1) + bundle_item = "_Test Bundle" + if not frappe.db.exists("Item", bundle_item): + create_item( + item_code=bundle_item, + is_stock_item=0, + ) + + if not frappe.db.exists("Product Bundle", bundle_item): + make_product_bundle(parent=bundle_item, items=[sub_item_a, sub_item_b]) + + # Test Case 1: Sufficient stock (bundle qty=1: requires SubA=2 (<=5), SubB=1 (<=2)) -> No error + pos_inv_sufficient = create_pos_invoice( + item=bundle_item, + qty=1, + rate=100, + warehouse=warehouse, + pos_profile=self.pos_profile.name, + do_not_save=1, + ) + pos_inv_sufficient.append("payments", {"mode_of_payment": "Cash", "amount": 100, "default": 1}) + pos_inv_sufficient.insert() + pos_inv_sufficient.submit() + + pos_inv_sufficient.cancel() + pos_inv_sufficient.delete() + + # Test Case 2: Insufficient stock (reduce SubB to 1, bundle qty=2: requires SubB=2 >1) -> Error with details + make_stock_entry(item_code=sub_item_b, from_warehouse=warehouse, qty=1, company=company) + + pos_inv_insufficient = create_pos_invoice( + item=bundle_item, + qty=2, + rate=100, + warehouse=warehouse, + pos_profile=self.pos_profile.name, + do_not_save=1, + ) + pos_inv_insufficient.append("payments", {"mode_of_payment": "Cash", "amount": 200, "default": 1}) + pos_inv_insufficient.save() + self.assertRaises(ProductBundleStockValidationError, pos_inv_insufficient.submit) + + frappe.set_user("test@example.com") + def create_pos_invoice(**args): args = frappe._dict(args)