From aba5b1ed53f9d9c7031041337c70c99f4d871d90 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 26 Jun 2026 06:12:13 +0000 Subject: [PATCH 1/2] fix: handle None local_terms in get_terms The AI response may omit `local_terms` or return it as null, in which case `self.data.local_terms` resolves to None and accessing `.description`/`.incoterms` on it raises AttributeError during Sales Order generation. Fall back to an empty frappe._dict so the attribute access resolves to None gracefully and an empty terms string is returned. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01B3hHouqds5m6xkcYetE8KY --- transaction_parser/tests/test_sales_order.py | 41 +++++++++++++++++++ .../controllers/transaction.py | 8 ++-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/transaction_parser/tests/test_sales_order.py b/transaction_parser/tests/test_sales_order.py index 1f419a6..3414896 100644 --- a/transaction_parser/tests/test_sales_order.py +++ b/transaction_parser/tests/test_sales_order.py @@ -300,6 +300,47 @@ def test_item_processing(self, mock_parse): self.assertEqual(item.doctype, "Sales Order Item") self.assertEqual(item.parentfield, "items") + # ---------------------- + # Terms and Conditions Tests + # ---------------------- + + def test_get_terms_with_local_terms(self): + """Test terms generation from local_terms data.""" + controller = SalesOrder() + controller.data = self.sample_data + + terms = controller.get_terms() + + self.assertIn("Standard terms and conditions", terms) + self.assertIn("Incoterms: EXW", terms) + + def test_get_terms_with_missing_local_terms(self): + """Test terms generation does not fail when local_terms is None. + + The AI response may omit `local_terms` or return it as null, so + get_terms must handle a None value gracefully instead of raising + AttributeError. + """ + modified_data = copy.deepcopy(self.sample_data) + modified_data.local_terms = None + + controller = SalesOrder() + controller.data = modified_data + + self.assertEqual(controller.get_terms(), "") + + def test_get_terms_with_partial_local_terms(self): + """Test terms generation when local_terms has only a description.""" + modified_data = copy.deepcopy(self.sample_data) + modified_data.local_terms = frappe._dict( + {"description": "Only description", "incoterms": None} + ) + + controller = SalesOrder() + controller.data = modified_data + + self.assertEqual(controller.get_terms(), "Only description") + # ---------------------- # Integration Tests # ---------------------- diff --git a/transaction_parser/transaction_parser/controllers/transaction.py b/transaction_parser/transaction_parser/controllers/transaction.py index eca493e..dcf5545 100644 --- a/transaction_parser/transaction_parser/controllers/transaction.py +++ b/transaction_parser/transaction_parser/controllers/transaction.py @@ -496,11 +496,11 @@ def get_payment_schedule_doc(self, term): ### Terms and Conditions def get_terms(self) -> str: - terms = ( - description if (description := self.data.local_terms.description) else "" - ) + local_terms = self.data.local_terms or frappe._dict() + + terms = local_terms.description or "" - if incoterms := self.data.local_terms.incoterms: + if incoterms := local_terms.incoterms: terms += f"\nIncoterms: {incoterms}" return terms From ba5c3bf39b3251b5467762194d739de436a211b0 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 26 Jun 2026 06:20:04 +0000 Subject: [PATCH 2/2] test: add omitted local_terms key case for get_terms Covers the truly-absent key path (not just an explicit None value), confirming get_terms relies on frappe._dict returning None for a missing attribute. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01B3hHouqds5m6xkcYetE8KY --- transaction_parser/tests/test_sales_order.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/transaction_parser/tests/test_sales_order.py b/transaction_parser/tests/test_sales_order.py index 3414896..72e156f 100644 --- a/transaction_parser/tests/test_sales_order.py +++ b/transaction_parser/tests/test_sales_order.py @@ -341,6 +341,22 @@ def test_get_terms_with_partial_local_terms(self): self.assertEqual(controller.get_terms(), "Only description") + def test_get_terms_with_missing_local_terms_key(self): + """Test terms generation when the local_terms key is completely absent. + + Unlike an explicit None value, this verifies get_terms relies on + frappe._dict returning None for a missing attribute rather than the + key merely being present with a null value. + """ + modified_data = copy.deepcopy(self.sample_data) + if hasattr(modified_data, "local_terms"): + delattr(modified_data, "local_terms") + + controller = SalesOrder() + controller.data = modified_data + + self.assertEqual(controller.get_terms(), "") + # ---------------------- # Integration Tests # ----------------------