fix: [30388f42] Mache den Worker robust gegenüber gelöschten Entitäten

- Fügt eine  zum  hinzu, die bei einem HTTP 404 Fehler ausgelöst wird.
- Fängt diese  im  ab.
- Markiert Jobs, die sich auf nicht (mehr) existierende Kontakte oder Personen beziehen, als  anstatt .
- Dies verhindert, dass die Fehlerwarteschlange mit Jobs für gelöschte Entitäten überläuft, was das Hauptproblem der "failed"-Jobs löst.
This commit is contained in:
2026-03-06 12:30:40 +00:00
parent 846bfaf999
commit 7d4e1d5aaa
2 changed files with 27 additions and 8 deletions

View File

@@ -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:

View File

@@ -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}")
@@ -198,6 +201,10 @@ def process_job(job, so_client: SuperOfficeClient, queue: JobQueue):
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)