From 61c2e7ad6ec2a4733abf2151e7a24de5e730f333 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 22 Jun 2026 12:33:19 +0530 Subject: [PATCH] test(opportunity): cover the mark-as-lost flow declare_enquiry_lost had almost no coverage. Add tests that marking an Opportunity as lost records the lost reasons, competitors and detailed reason and sets status to Lost, and that it is blocked when an active (submitted) Quotation exists. --- .../doctype/opportunity/test_opportunity.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/erpnext/crm/doctype/opportunity/test_opportunity.py b/erpnext/crm/doctype/opportunity/test_opportunity.py index 62fad25a574..815ab992412 100644 --- a/erpnext/crm/doctype/opportunity/test_opportunity.py +++ b/erpnext/crm/doctype/opportunity/test_opportunity.py @@ -100,6 +100,42 @@ class TestOpportunity(ERPNextTestSuite): opp.opportunity_owner = None self.assertIsNone(opp.get_notification_email()) + def test_declare_enquiry_lost(self): + lost_reason = _ensure_master("Opportunity Lost Reason", "lost_reason", "_Test Lost - Too Expensive") + competitor = _ensure_master("Competitor", "competitor_name", "_Test Competitor") + + opp = make_opportunity(with_items=0) + opp.declare_enquiry_lost( + lost_reasons_list=[{"lost_reason": lost_reason}], + competitors=[{"competitor": competitor}], + detailed_reason="Budget too high", + ) + + opp.reload() + self.assertEqual(opp.status, "Lost") + self.assertEqual(opp.order_lost_reason, "Budget too high") + self.assertEqual([d.lost_reason for d in opp.lost_reasons], [lost_reason]) + self.assertEqual([d.competitor for d in opp.competitors], [competitor]) + + def test_declare_lost_blocked_when_quotation_active(self): + opp = make_opportunity(with_items=0) + quotation = make_quotation(opp.name) + quotation.append("items", {"item_code": "_Test Item", "qty": 1}) + quotation.run_method("set_missing_values") + quotation.run_method("calculate_taxes_and_totals") + quotation.submit() + + # A submitted, still-active quotation exists, so the opportunity can't be marked lost + opp.reload() + self.assertRaises(frappe.ValidationError, opp.declare_enquiry_lost, [], [], "x") + self.assertNotEqual(opp.status, "Lost") + + +def _ensure_master(doctype, fieldname, value): + if not frappe.db.exists(doctype, value): + frappe.get_doc({"doctype": doctype, fieldname: value}).insert(ignore_permissions=True) + return value + def make_opportunity_from_lead(company): new_lead_email_id = f"new{random_string(5)}@example.com"