[2ff88f42] 1. Umfassende Entitäten-Erstellung: Wir haben erfolgreich Methoden implementiert, um die Kern-SuperOffice-Entitäten per API zu erstellen:
1. Umfassende Entitäten-Erstellung: Wir haben erfolgreich Methoden implementiert, um die Kern-SuperOffice-Entitäten per API zu erstellen:
* Firmen (`Contact`)
* Personen (`Person`)
* Verkäufe (`Sale`) (entspricht D365 Opportunity)
* Projekte (`Project`) (entspricht D365 Campaign), inklusive der Verknüpfung von Personen als Projektmitglieder.
2. Robuste UDF-Aktualisierung: Wir haben eine generische und fehlertolerante Methode (update_entity_udfs) implementiert, die benutzerdefinierte Felder (UDFs) für sowohl Contact- als
auch Person-Entitäten aktualisieren kann. Diese Methode ruft zuerst das bestehende Objekt ab, um die Konsistenz zu gewährleisten.
3. UDF-ID-Discovery: Durch eine iterative Inspektionsmethode haben wir erfolgreich alle internen SuperOffice-IDs für die Listenwerte deines MA Status-Feldes (Ready_to_Send, Sent_Week1,
Sent_Week2, Bounced, Soft_Denied, Interested, Out_of_Office, Unsubscribed) ermittelt und im Connector hinterlegt.
4. Vollständiger End-to-End Test-Workflow: Unser main.py-Skript demonstriert nun einen kompletten Ablauf, der alle diese Schritte von der Erstellung bis zur UDF-Aktualisierung umfasst.
5. Architekturplan für Marketing Automation: Wir haben einen detaillierten "Butler-Service"-Architekturplan für die Marketing-Automatisierung entworfen, der den Connector für die
Textgenerierung und SuperOffice für den Versand und das Status-Management nutzt.
6. Identifikation des E-Mail-Blockers: Wir haben festgestellt, dass das Erstellen von E-Mail-Aktivitäten per API in deiner aktuellen SuperOffice-Entwicklungsumgebung aufgrund fehlender
Lizenzierung/Konfiguration des E-Mail-Moduls blockiert ist (500 Internal Server Error).
This commit is contained in:
@@ -12,21 +12,30 @@ class SuperOfficeClient:
|
||||
self.auth_handler = auth_handler
|
||||
self.session = requests.Session()
|
||||
|
||||
# Mapping for UDF fields (These are typical technical names, but might need adjustment)
|
||||
self.udf_mapping = {
|
||||
"robotics_potential": "x_robotics_potential",
|
||||
"industry": "x_ai_industry",
|
||||
"summary": "x_ai_summary",
|
||||
"last_update": "x_ai_last_update",
|
||||
"status": "x_ai_status"
|
||||
# Mapping for UDF fields for Contact entity
|
||||
self.udf_contact_mapping = {
|
||||
"ai_challenge_sentence": "SuperOffice:1",
|
||||
"ai_sentence_timestamp": "SuperOffice:2",
|
||||
"ai_sentence_source_hash": "SuperOffice:3",
|
||||
"ai_last_outreach_date": "SuperOffice:4"
|
||||
}
|
||||
|
||||
# Mapping for UDF fields for Person entity
|
||||
self.udf_person_mapping = {
|
||||
"ai_email_draft": "SuperOffice:1", # NOTE: This is currently a Date field in SO and needs to be changed to Text (Long/Memo)
|
||||
"ma_status": "SuperOffice:2"
|
||||
}
|
||||
|
||||
# Mapping for list values (Explorer -> SO ID)
|
||||
self.potential_id_map = {
|
||||
"High": 1,
|
||||
"Medium": 2,
|
||||
"Low": 3,
|
||||
"None": 4
|
||||
# Mapping for MA Status list values (Text Label -> SO ID)
|
||||
self.ma_status_id_map = {
|
||||
"Ready_to_Send": 11,
|
||||
"Sent_Week1": 12,
|
||||
"Sent_Week2": 13,
|
||||
"Bounced": 14,
|
||||
"Soft_Denied": 15,
|
||||
"Interested": 16,
|
||||
"Out_of_Office": 17,
|
||||
"Unsubscribed": 18
|
||||
}
|
||||
|
||||
def _get_headers(self):
|
||||
@@ -220,3 +229,176 @@ class SuperOfficeClient:
|
||||
if hasattr(e, 'response') and e.response is not None:
|
||||
logger.error(f"Response: {e.response.text}")
|
||||
return None
|
||||
|
||||
def update_entity_udfs(self, entity_id, entity_type, udf_data: dict):
|
||||
"""Updates user-defined fields for a given entity (Contact or Person)."""
|
||||
if entity_type not in ["Contact", "Person"]:
|
||||
logger.error(f"Invalid entity_type: {entity_type}. Must be 'Contact' or 'Person'.")
|
||||
return None
|
||||
|
||||
# 1. Retrieve the existing entity to ensure all required fields are present in the PUT payload
|
||||
get_url = self._get_url(f"v1/{entity_type}/{entity_id}")
|
||||
try:
|
||||
get_resp = self.session.get(get_url, headers=self._get_headers())
|
||||
get_resp.raise_for_status()
|
||||
existing_entity = get_resp.json()
|
||||
logger.info(f"Successfully retrieved existing {entity_type} ID {entity_id}.")
|
||||
except Exception as e:
|
||||
logger.error(f"Error retrieving existing {entity_type} ID {entity_id}: {e}")
|
||||
if hasattr(e, 'response') and e.response is not None:
|
||||
logger.error(f"Response: {e.response.text}")
|
||||
return None
|
||||
|
||||
# Use the existing entity data as the base for the PUT payload
|
||||
payload = existing_entity
|
||||
if "UserDefinedFields" not in payload:
|
||||
payload["UserDefinedFields"] = {}
|
||||
|
||||
# Select the correct mapping based on entity type
|
||||
udf_mapping = self.udf_contact_mapping if entity_type == "Contact" else self.udf_person_mapping
|
||||
|
||||
for key, value in udf_data.items():
|
||||
prog_id = udf_mapping.get(key)
|
||||
if prog_id:
|
||||
if key == "ma_status" and entity_type == "Person":
|
||||
# For MA Status, we need to send the internal ID directly as an integer
|
||||
internal_id = self.ma_status_id_map.get(value)
|
||||
if internal_id:
|
||||
payload["UserDefinedFields"][prog_id] = internal_id
|
||||
else:
|
||||
logger.warning(f"Unknown MA Status value '{value}'. Skipping update for {key}.")
|
||||
else:
|
||||
# For other UDFs, send the value directly
|
||||
payload["UserDefinedFields"][prog_id] = value
|
||||
else:
|
||||
logger.warning(f"Unknown UDF key for {entity_type}: {key}. Skipping.")
|
||||
|
||||
if not payload["UserDefinedFields"]:
|
||||
logger.info(f"No valid UDF data to update for {entity_type} ID {entity_id}.")
|
||||
return None
|
||||
|
||||
# 2. Send the updated entity (including all original fields + modified UDFs) via PUT
|
||||
put_url = self._get_url(f"v1/{entity_type}/{entity_id}")
|
||||
try:
|
||||
logger.info(f"Attempting to update UDFs for {entity_type} ID {entity_id} with: {payload['UserDefinedFields']}")
|
||||
resp = self.session.put(put_url, headers=self._get_headers(), json=payload)
|
||||
resp.raise_for_status()
|
||||
updated_entity = resp.json()
|
||||
logger.info(f"Successfully updated UDFs for {entity_type} ID {entity_id}.")
|
||||
return updated_entity
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating UDFs for {entity_type} ID {entity_id}: {e}")
|
||||
if hasattr(e, 'response') and e.response is not None:
|
||||
logger.error(f"Response: {e.response.text}")
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# NOTE: The create_email_activity method is currently blocked due to SuperOffice environment limitations.
|
||||
|
||||
|
||||
# Attempting to create an Email Activity via API results in a 500 Internal Server Error,
|
||||
|
||||
|
||||
# likely because the email module is not licensed or configured in the SOD environment.
|
||||
|
||||
|
||||
# This method is temporarily commented out.
|
||||
|
||||
|
||||
#
|
||||
|
||||
|
||||
# def create_email_activity(self, person_id, contact_id, subject, body):
|
||||
|
||||
|
||||
# """Creates an Email Activity linked to a person and contact."""
|
||||
|
||||
|
||||
# url = self._get_url("v1/Activity")
|
||||
|
||||
|
||||
#
|
||||
|
||||
|
||||
# payload = {
|
||||
|
||||
|
||||
# "Type": { # Assuming ID 2 for "Email" ActivityType
|
||||
|
||||
|
||||
# "Id": 2
|
||||
|
||||
|
||||
# },
|
||||
|
||||
|
||||
# "Title": subject,
|
||||
|
||||
|
||||
# "Details": body,
|
||||
|
||||
|
||||
# "Person": {
|
||||
|
||||
|
||||
# "PersonId": person_id
|
||||
|
||||
|
||||
# },
|
||||
|
||||
|
||||
# "Contact": {
|
||||
|
||||
|
||||
# "ContactId": contact_id
|
||||
|
||||
|
||||
# }
|
||||
|
||||
|
||||
# }
|
||||
|
||||
|
||||
#
|
||||
|
||||
|
||||
# try:
|
||||
|
||||
|
||||
# logger.info(f"Attempting to create Email Activity with subject '{subject}' for Person ID {person_id} and Contact ID {contact_id}")
|
||||
|
||||
|
||||
# resp = self.session.post(url, headers=self._get_headers(), json=payload)
|
||||
|
||||
|
||||
# resp.raise_for_status()
|
||||
|
||||
|
||||
# created_activity = resp.json()
|
||||
|
||||
|
||||
# logger.info(f"Successfully created Email Activity: '{created_activity.get('Title')}' (ID: {created_activity.get('ActivityId')})")
|
||||
|
||||
|
||||
# return created_activity
|
||||
|
||||
|
||||
# except Exception as e:
|
||||
|
||||
|
||||
# logger.error(f"Error creating Email Activity: {e}")
|
||||
|
||||
|
||||
# if hasattr(e, 'response') and e.response is not None:
|
||||
|
||||
|
||||
# logger.error(f"Response: {e.response.text}")
|
||||
|
||||
|
||||
# return None
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user