diff --git a/connector-superoffice/superoffice_client.py b/connector-superoffice/superoffice_client.py index 55235832..3c5abd4c 100644 --- a/connector-superoffice/superoffice_client.py +++ b/connector-superoffice/superoffice_client.py @@ -8,6 +8,10 @@ import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger("superoffice-client") +class ContactNotFoundException(Exception): + """Custom exception for 404 errors on Contact/Person lookups.""" + pass + class SuperOfficeClient: """A client for interacting with the SuperOffice REST API.""" @@ -117,6 +121,11 @@ class SuperOfficeClient: return resp.json() except requests.exceptions.HTTPError as e: + # Explicitly handle 404 Not Found for GET requests + if method == "GET" and e.response.status_code == 404: + logger.warning(f"🔍 404 Not Found for GET request to {endpoint}.") + raise ContactNotFoundException(f"Entity not found at {endpoint}") from e + logger.error(f"❌ API {method} Error for {endpoint} (Status: {e.response.status_code}): {e.response.text}") return None except Exception as e: diff --git a/connector-superoffice/worker.py b/connector-superoffice/worker.py index ac6886f1..2bd593f6 100644 --- a/connector-superoffice/worker.py +++ b/connector-superoffice/worker.py @@ -5,7 +5,7 @@ import requests import json from datetime import datetime from queue_manager import JobQueue -from superoffice_client import SuperOfficeClient +from superoffice_client import SuperOfficeClient, ContactNotFoundException from config import settings # Setup Logging @@ -39,7 +39,7 @@ def process_job(job, so_client: SuperOfficeClient, queue: JobQueue): Returns: (STATUS, MESSAGE) STATUS: 'SUCCESS', 'SKIPPED', 'RETRY', 'FAILED' """ - logger.info(f"--- [WORKER v1.9.1] Processing Job {job['id']} ({job['event_type']}) ---") + logger.info(f"--- [WORKER v1.9.2] Processing Job {job['id']} ({job['event_type']}) ---") payload = job['payload'] event_low = job['event_type'].lower() @@ -93,6 +93,10 @@ def process_job(job, so_client: SuperOfficeClient, queue: JobQueue): contact_id = contact_obj.get("ContactId") elif "ContactId" in person_details: contact_id = person_details.get("ContactId") + except ContactNotFoundException: + msg = f"Skipping job because Person ID {person_id} was not found in SuperOffice (likely deleted)." + logger.warning(msg) + return ("SKIPPED", msg) except Exception as e: logger.warning(f"Failed to fetch person details for {person_id}: {e}") @@ -121,10 +125,6 @@ def process_job(job, so_client: SuperOfficeClient, queue: JobQueue): select=["Name", "UrlAddress", "Urls", "UserDefinedFields", "Address", "OrgNr", "Associate"] ) - # ABSOLUTE SAFETY CHECK - if contact_details is None: - raise ValueError(f"SuperOffice API returned None for Contact {contact_id}. Possible timeout or record locked.") - crm_name = contact_details.get("Name", "Unknown") # Safely get Associate object @@ -181,6 +181,9 @@ def process_job(job, so_client: SuperOfficeClient, queue: JobQueue): logger.info(f"đŸŽ¯ CAMPAIGN DETECTED: '{campaign_tag}'") else: logger.info("â„šī¸ No Campaign Tag found (Field is empty).") + except ContactNotFoundException: + # This is not critical, we can proceed without a campaign tag + logger.warning(f"Could not fetch campaign tag: Person {person_id} not found.") except Exception as e: logger.warning(f"Could not fetch campaign tag: {e}") @@ -197,7 +200,11 @@ def process_job(job, so_client: SuperOfficeClient, queue: JobQueue): crm_industry_name = vertical_map_rev[val_str] except Exception as ex: logger.error(f"Error mapping vertical ID {val_str}: {ex}") - + + except ContactNotFoundException: + msg = f"Skipping job because Contact ID {contact_id} was not found in SuperOffice (likely deleted)." + logger.warning(msg) + return ("SKIPPED", msg) except Exception as e: logger.error(f"Failed to fetch contact details for {contact_id}: {e}") raise Exception(f"SuperOffice API Failure: {e}") @@ -231,7 +238,10 @@ def process_job(job, so_client: SuperOfficeClient, queue: JobQueue): # Fetch fresh Contact Data for comparison contact_data = so_client.get_contact(contact_id) - if not contact_data: return ("FAILED", "Could not fetch contact for patch") + if not contact_data: + # This can happen if the contact was deleted between the CE call and now + logger.warning(f"Could not re-fetch contact {contact_id} for patch (deleted?). Skipping patch.") + return ("SKIPPED", "Contact deleted post-analysis") # SAFE GET FOR COMPARISON current_udfs = safe_get_udfs(contact_data)