mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-17 00:18:39 +00:00
test(selling): cover disabled Product Bundle behaviour
Resolution skips disabled bundles, transactions referencing a disabled
version are blocked, rows without an explicit version stop packing, and
the Item Where Used report surfaces the disabled flag on bundle rows.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit c97c2d1e02)
# Conflicts:
# erpnext/selling/doctype/product_bundle/test_product_bundle.py
# erpnext/stock/doctype/packed_item/test_packed_item.py
This commit is contained in:
@@ -16,3 +16,119 @@ def make_product_bundle(parent, items, qty=None):
|
||||
product_bundle.insert()
|
||||
|
||||
return product_bundle
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
|
||||
|
||||
class TestProductBundle(ERPNextTestSuite):
|
||||
def setUp(self):
|
||||
self.parent = make_item("_Test PB Parent", {"is_stock_item": 0, "is_sales_item": 1}).name
|
||||
make_item("_Test PB Child A", {"is_stock_item": 1})
|
||||
make_item("_Test PB Child B", {"is_stock_item": 1})
|
||||
|
||||
def test_submit_makes_bundle_active_and_versioned(self):
|
||||
bundle = make_product_bundle(self.parent, ["_Test PB Child A"])
|
||||
self.assertEqual(bundle.docstatus, 1)
|
||||
self.assertEqual(bundle.is_active, 1)
|
||||
self.assertTrue(bundle.name.startswith("PB-"))
|
||||
self.assertEqual(get_active_product_bundle(self.parent), bundle.name)
|
||||
|
||||
def test_new_version_deactivates_previous(self):
|
||||
v1 = make_product_bundle(self.parent, ["_Test PB Child A"])
|
||||
|
||||
v2 = make_new_version(v1.name)
|
||||
v2.items[0].qty = 5
|
||||
v2.insert()
|
||||
v2.submit()
|
||||
|
||||
self.assertNotEqual(v1.name, v2.name)
|
||||
self.assertEqual(get_active_product_bundle(self.parent), v2.name)
|
||||
self.assertEqual(frappe.db.get_value("Product Bundle", v1.name, "is_active"), 0)
|
||||
|
||||
def test_reactivating_old_version_deactivates_current(self):
|
||||
v1 = make_product_bundle(self.parent, ["_Test PB Child A"])
|
||||
|
||||
v2 = make_new_version(v1.name)
|
||||
v2.items[0].qty = 5
|
||||
v2.insert()
|
||||
v2.submit()
|
||||
self.assertEqual(get_active_product_bundle(self.parent), v2.name)
|
||||
|
||||
# switch back to v1 by toggling is_active on the submitted doc (allow_on_submit)
|
||||
v1.reload()
|
||||
v1.is_active = 1
|
||||
v1.save()
|
||||
|
||||
self.assertEqual(get_active_product_bundle(self.parent), v1.name)
|
||||
self.assertEqual(frappe.db.get_value("Product Bundle", v2.name, "is_active"), 0)
|
||||
|
||||
def test_new_bundle_from_scratch_supersedes_existing(self):
|
||||
# An item that already has a bundle must remain selectable so a new version
|
||||
# can be created straight from the New Product Bundle form.
|
||||
from erpnext.selling.doctype.product_bundle.product_bundle import get_new_item_code
|
||||
|
||||
v1 = make_product_bundle(self.parent, ["_Test PB Child A"])
|
||||
|
||||
picker = [row[0] for row in get_new_item_code("Item", self.parent, "name", 0, 20, {})]
|
||||
self.assertIn(self.parent, picker)
|
||||
|
||||
v2 = frappe.get_doc({"doctype": "Product Bundle", "new_item_code": self.parent})
|
||||
v2.append("items", {"item_code": "_Test PB Child B", "qty": 1})
|
||||
v2.insert()
|
||||
v2.submit()
|
||||
|
||||
self.assertNotEqual(v1.name, v2.name)
|
||||
self.assertEqual(get_active_product_bundle(self.parent), v2.name)
|
||||
self.assertEqual(frappe.db.get_value("Product Bundle", v1.name, "is_active"), 0)
|
||||
|
||||
def test_cancel_clears_active(self):
|
||||
bundle = make_product_bundle(self.parent, ["_Test PB Child A"])
|
||||
bundle.cancel()
|
||||
self.assertEqual(frappe.db.get_value("Product Bundle", bundle.name, "is_active"), 0)
|
||||
self.assertIsNone(get_active_product_bundle(self.parent))
|
||||
|
||||
def test_submitted_bundle_is_immutable(self):
|
||||
bundle = make_product_bundle(self.parent, ["_Test PB Child A"])
|
||||
bundle.items[0].qty = 99
|
||||
self.assertRaises(frappe.exceptions.UpdateAfterSubmitError, bundle.save)
|
||||
|
||||
def test_disabled_bundle_is_not_resolved(self):
|
||||
bundle = make_product_bundle(self.parent, ["_Test PB Child A"])
|
||||
|
||||
bundle.disabled = 1
|
||||
bundle.save()
|
||||
self.assertIsNone(get_active_product_bundle(self.parent))
|
||||
|
||||
# disabling parks the version without ceding the active slot, so re-enabling
|
||||
# restores resolution without re-activation
|
||||
self.assertEqual(frappe.db.get_value("Product Bundle", bundle.name, "is_active"), 1)
|
||||
bundle.disabled = 0
|
||||
bundle.save()
|
||||
self.assertEqual(get_active_product_bundle(self.parent), bundle.name)
|
||||
|
||||
def test_item_where_used_report_shows_disabled_flag(self):
|
||||
from erpnext.stock.report.item_where_used.item_where_used import execute
|
||||
|
||||
bundle = make_product_bundle(self.parent, ["_Test PB Child A"])
|
||||
bundle.disabled = 1
|
||||
bundle.save()
|
||||
|
||||
_, component_rows = execute({"item": "_Test PB Child A", "section": "Where Used"})
|
||||
rows = [r for r in component_rows if r.document_name == bundle.name]
|
||||
self.assertTrue(rows)
|
||||
self.assertEqual(rows[0].disabled, 1)
|
||||
self.assertEqual(rows[0].is_active, 1)
|
||||
|
||||
_, parent_rows = execute({"item": self.parent, "section": "References"})
|
||||
rows = [r for r in parent_rows if r.document_name == bundle.name]
|
||||
self.assertTrue(rows)
|
||||
self.assertEqual(rows[0].disabled, 1)
|
||||
|
||||
def test_child_cannot_be_active_bundle(self):
|
||||
make_product_bundle(self.parent, ["_Test PB Child A"])
|
||||
outer = make_item("_Test PB Outer", {"is_stock_item": 0, "is_sales_item": 1}).name
|
||||
|
||||
doc = frappe.get_doc({"doctype": "Product Bundle", "new_item_code": outer})
|
||||
doc.append("items", {"item_code": self.parent, "qty": 1})
|
||||
self.assertRaises(frappe.ValidationError, doc.insert)
|
||||
>>>>>>> c97c2d1e02 (test(selling): cover disabled Product Bundle behaviour)
|
||||
|
||||
@@ -76,6 +76,120 @@ class TestPackedItem(ERPNextTestSuite):
|
||||
|
||||
self.assertEqual(len(so.packed_items), 0)
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
def test_item_and_packed_rows_record_bundle_version(self):
|
||||
"The item row and its packed items record the resolved Product Bundle version."
|
||||
from erpnext.selling.doctype.product_bundle.product_bundle import get_active_product_bundle
|
||||
|
||||
version = get_active_product_bundle(self.bundle)
|
||||
self.assertTrue(version and version.startswith("PB-"))
|
||||
|
||||
so = make_sales_order(item_code=self.bundle, qty=1, warehouse=self.warehouse)
|
||||
self.assertEqual(so.items[0].product_bundle, version)
|
||||
self.assertEqual(so.items[0].is_product_bundle, 1)
|
||||
self.assertEqual(len(so.packed_items), 2)
|
||||
for pi in so.packed_items:
|
||||
self.assertEqual(pi.product_bundle, version)
|
||||
|
||||
# the version carries onto a Delivery Note mapped from the Sales Order
|
||||
dn = make_delivery_note(so.name)
|
||||
self.assertEqual(dn.items[0].product_bundle, version)
|
||||
for pi in dn.packed_items:
|
||||
self.assertEqual(pi.product_bundle, version)
|
||||
|
||||
def test_clearing_version_keeps_bundle_flag_and_redefaults(self):
|
||||
"Clearing the version must not lose the bundle flag (keeps the field visible)."
|
||||
so = make_sales_order(item_code=self.bundle, qty=1, warehouse=self.warehouse, do_not_submit=True)
|
||||
version = so.items[0].product_bundle
|
||||
self.assertEqual(so.items[0].is_product_bundle, 1)
|
||||
|
||||
# user blanks the version field
|
||||
so.items[0].product_bundle = None
|
||||
so.save()
|
||||
|
||||
# the flag stays set (so depends_on keeps the field visible) and the value
|
||||
# re-defaults to the active version
|
||||
self.assertEqual(so.items[0].is_product_bundle, 1)
|
||||
self.assertEqual(so.items[0].product_bundle, version)
|
||||
|
||||
def test_backfill_patch_stamps_existing_rows(self):
|
||||
"The backfill patch stamps the version on rows that predate the field."
|
||||
from erpnext.patches.v16_0.submit_existing_product_bundles import (
|
||||
stamp_versions_on_transactions as stamp_versions,
|
||||
)
|
||||
from erpnext.selling.doctype.product_bundle.product_bundle import get_active_product_bundle
|
||||
|
||||
version = get_active_product_bundle(self.bundle)
|
||||
so = make_sales_order(item_code=self.bundle, qty=1, do_not_submit=True)
|
||||
|
||||
# simulate pre-migration rows with no version recorded and no bundle flag
|
||||
frappe.db.set_value("Sales Order Item", so.items[0].name, "product_bundle", None)
|
||||
frappe.db.set_value("Sales Order Item", so.items[0].name, "is_product_bundle", 0)
|
||||
for pi in so.packed_items:
|
||||
frappe.db.set_value("Packed Item", pi.name, "product_bundle", None)
|
||||
|
||||
stamp_versions()
|
||||
|
||||
self.assertEqual(frappe.db.get_value("Sales Order Item", so.items[0].name, "product_bundle"), version)
|
||||
self.assertEqual(frappe.db.get_value("Sales Order Item", so.items[0].name, "is_product_bundle"), 1)
|
||||
for pi in so.packed_items:
|
||||
self.assertEqual(frappe.db.get_value("Packed Item", pi.name, "product_bundle"), version)
|
||||
|
||||
def test_choosing_an_older_version_packs_its_components(self):
|
||||
"Default picks the active version; choosing an older version re-packs its components."
|
||||
from erpnext.selling.doctype.product_bundle.product_bundle import (
|
||||
get_active_product_bundle,
|
||||
make_new_version,
|
||||
)
|
||||
|
||||
v1 = get_active_product_bundle(self.bundle)
|
||||
|
||||
# new version with a different component becomes the active one
|
||||
new_component = make_item().name
|
||||
make_stock_entry(item=new_component, to_warehouse=self.warehouse, qty=50, rate=100)
|
||||
v2 = make_new_version(v1)
|
||||
v2.items = []
|
||||
v2.append("items", {"item_code": new_component, "qty": 1})
|
||||
v2.insert()
|
||||
v2.submit()
|
||||
self.assertEqual(get_active_product_bundle(self.bundle), v2.name)
|
||||
|
||||
# default: the active version (v2) and its component
|
||||
so = make_sales_order(item_code=self.bundle, qty=1, warehouse=self.warehouse, do_not_submit=True)
|
||||
self.assertEqual(so.items[0].product_bundle, v2.name)
|
||||
self.assertEqual([pi.item_code for pi in so.packed_items], [new_component])
|
||||
|
||||
# choose the older version -> its components are packed instead
|
||||
so.items[0].product_bundle = v1
|
||||
so.save()
|
||||
self.assertEqual(so.items[0].product_bundle, v1)
|
||||
self.assertEqual(sorted(pi.item_code for pi in so.packed_items), sorted(self.bundle_items))
|
||||
|
||||
def test_disabled_bundle_blocks_transaction(self):
|
||||
"A row that explicitly references a disabled version cannot be saved."
|
||||
from erpnext.selling.doctype.product_bundle.product_bundle import get_active_product_bundle
|
||||
|
||||
version = get_active_product_bundle(self.bundle)
|
||||
so = make_sales_order(item_code=self.bundle, qty=1, warehouse=self.warehouse, do_not_submit=True)
|
||||
self.assertEqual(so.items[0].product_bundle, version)
|
||||
|
||||
frappe.db.set_value("Product Bundle", version, "disabled", 1)
|
||||
self.assertRaises(frappe.ValidationError, so.save)
|
||||
|
||||
def test_disabled_bundle_is_not_packed(self):
|
||||
"Without an explicit version, a disabled bundle is not treated as a bundle at all."
|
||||
from erpnext.selling.doctype.product_bundle.product_bundle import get_active_product_bundle
|
||||
|
||||
version = get_active_product_bundle(self.bundle2)
|
||||
frappe.db.set_value("Product Bundle", version, "disabled", 1)
|
||||
|
||||
so = make_sales_order(item_code=self.bundle2, qty=1, warehouse=self.warehouse, do_not_submit=True)
|
||||
self.assertEqual(so.items[0].is_product_bundle, 0)
|
||||
self.assertFalse(so.items[0].product_bundle)
|
||||
self.assertFalse(so.get("packed_items"))
|
||||
|
||||
>>>>>>> c97c2d1e02 (test(selling): cover disabled Product Bundle behaviour)
|
||||
@ERPNextTestSuite.change_settings("Selling Settings", {"allow_multiple_items": 1})
|
||||
def test_recurring_bundle_item(self):
|
||||
"Test impact on packed items if same bundle item is added and removed."
|
||||
|
||||
Reference in New Issue
Block a user