[30388f42] Infrastructure Hardening: Repaired CE/Connector DB schema, fixed frontend styling build, implemented robust echo shield in worker v2.1.1, and integrated Lead Engine into gateway.
This commit is contained in:
45
connector-superoffice/tools/blind_check_associates.py
Normal file
45
connector-superoffice/tools/blind_check_associates.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# Absolute path setup
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
connector_dir = os.path.abspath(os.path.join(current_dir, '..'))
|
||||
sys.path.insert(0, connector_dir)
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
def blind_check():
|
||||
print("🕵️ Testing Manuel's Filter: contactAssociate/contactFullName eq 'RoboPlanet GmbH'")
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Auth failed.")
|
||||
return
|
||||
|
||||
# Manuel's filter logic with Count
|
||||
endpoint = "Contact?$filter=contactAssociate/contactFullName eq 'RoboPlanet GmbH'&$top=0&$count=true"
|
||||
|
||||
print(f"📡 Querying: {endpoint}")
|
||||
try:
|
||||
resp = client._get(endpoint)
|
||||
count = resp.get('@odata.count')
|
||||
print(f"\n🎯 RESULT: Manuel's Filter found {count} accounts.")
|
||||
|
||||
if count == 17014:
|
||||
print("✅ PERFECT MATCH! Manuel's filter matches your UI count exactly.")
|
||||
else:
|
||||
print(f"ℹ️ Delta to UI: {17014 - (count or 0)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Manuel's filter failed: {e}")
|
||||
# Try without spaces encoded
|
||||
print("Trying with encoded spaces...")
|
||||
try:
|
||||
endpoint_enc = "Contact?$filter=contactAssociate/contactFullName eq 'RoboPlanet+GmbH'&$top=0&$count=true"
|
||||
resp = client._get(endpoint_enc)
|
||||
print(f"🎯 Encoded Result: {resp.get('@odata.count')}")
|
||||
except:
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
blind_check()
|
||||
47
connector-superoffice/tools/check_contact_associate.py
Normal file
47
connector-superoffice/tools/check_contact_associate.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# Absolute path setup
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
connector_dir = os.path.abspath(os.path.join(current_dir, '..'))
|
||||
sys.path.insert(0, connector_dir)
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
def check_associate_details():
|
||||
print("🔎 Checking Associate Details in Contact Record...")
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Auth failed.")
|
||||
return
|
||||
|
||||
# Use our known test company (if it still exists - oh wait, we deleted it!)
|
||||
# We need to find ANY contact.
|
||||
|
||||
# Search for any contact
|
||||
print("Searching for a contact...")
|
||||
contacts = client.search("Contact?$top=1")
|
||||
|
||||
if contacts:
|
||||
cid = contacts[0].get('contactId') or contacts[0].get('ContactId')
|
||||
print(f"✅ Found Contact ID: {cid}")
|
||||
|
||||
# Fetch Full Details
|
||||
print("Fetching details...")
|
||||
details = client.get_contact(cid)
|
||||
|
||||
assoc = details.get('Associate')
|
||||
print("--- Associate Object ---")
|
||||
print(json.dumps(assoc, indent=2))
|
||||
|
||||
if assoc and 'GroupIdx' in assoc:
|
||||
print(f"✅ SUCCESS: GroupIdx is available: {assoc['GroupIdx']}")
|
||||
else:
|
||||
print("❌ FAILURE: GroupIdx is MISSING in Contact details.")
|
||||
|
||||
else:
|
||||
print("❌ No contacts found in system.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_associate_details()
|
||||
38
connector-superoffice/tools/check_filter_counts.py
Normal file
38
connector-superoffice/tools/check_filter_counts.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# Absolute path setup
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
connector_dir = os.path.abspath(os.path.join(current_dir, '..'))
|
||||
sys.path.insert(0, connector_dir)
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
def check_counts():
|
||||
print("📊 Verifying Filter Logic via OData Search...")
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Auth failed.")
|
||||
return
|
||||
|
||||
# Simplified OData Search
|
||||
# We ask for top=1 but want the total count
|
||||
endpoint = "Contact?$filter=name contains 'GmbH'&$top=1&$select=Associate"
|
||||
|
||||
print(f"📡 Querying: {endpoint}")
|
||||
try:
|
||||
resp = client._get(endpoint)
|
||||
print("--- RAW RESPONSE START ---")
|
||||
print(json.dumps(resp, indent=2))
|
||||
print("--- RAW RESPONSE END ---")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_counts()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_counts()
|
||||
52
connector-superoffice/tools/check_selection_members.py
Normal file
52
connector-superoffice/tools/check_selection_members.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# Absolute path setup
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
connector_dir = os.path.abspath(os.path.join(current_dir, '..'))
|
||||
sys.path.insert(0, connector_dir)
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
def check_selection():
|
||||
selection_id = 10960
|
||||
print(f"🔎 Inspecting Selection {selection_id} (Alle_Contacts_Roboplanet)...")
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Auth failed.")
|
||||
return
|
||||
|
||||
# 1. Get Selection Metadata
|
||||
print("\n📋 Fetching Selection Details...")
|
||||
details = client._get(f"Selection/{selection_id}")
|
||||
if details:
|
||||
print(f" Name: {details.get('Name')}")
|
||||
print(f" Description: {details.get('Description')}")
|
||||
print(f" Type: {details.get('SelectionType')}") # e.g. Dynamic, Static
|
||||
|
||||
# 2. Fetch Members via direct Selection endpoint
|
||||
print("\n👥 Fetching first 10 Members via direct Selection endpoint...")
|
||||
# Direct endpoint for Contact members of a selection
|
||||
endpoint = f"Selection/{selection_id}/ContactMembers?$top=10"
|
||||
|
||||
try:
|
||||
members_resp = client._get(endpoint)
|
||||
# OData usually returns a 'value' list
|
||||
members = members_resp.get('value', []) if isinstance(members_resp, dict) else members_resp
|
||||
|
||||
if members and isinstance(members, list):
|
||||
print(f"✅ Found {len(members)} members in first page:")
|
||||
for m in members:
|
||||
# Structure might be flat or nested
|
||||
name = m.get('Name') or m.get('name')
|
||||
cid = m.get('ContactId') or m.get('contactId')
|
||||
print(f" - {name} (ContactID: {cid})")
|
||||
else:
|
||||
print("⚠️ No members found or response format unexpected.")
|
||||
print(f"DEBUG: {json.dumps(members_resp, indent=2)}")
|
||||
except Exception as e:
|
||||
print(f"❌ Direct Selection members query failed: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_selection()
|
||||
@@ -0,0 +1,62 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# Absolute path setup
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
connector_dir = os.path.abspath(os.path.join(current_dir, '..'))
|
||||
sys.path.insert(0, connector_dir)
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
def run_discovery():
|
||||
print("🔎 Discovery: Searching for Selections and Associate Mapping...")
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Auth failed.")
|
||||
return
|
||||
|
||||
# 1. Search for Selections
|
||||
print("\n📁 Searching for 'Roboplanet' Selections...")
|
||||
# Selections can be found via Archive or direct endpoint
|
||||
selections = client.search("Selection?$filter=name contains 'Roboplanet'")
|
||||
if selections:
|
||||
print(f"✅ Found {len(selections)} matching selections:")
|
||||
for sel in selections:
|
||||
sid = sel.get('SelectionId') or sel.get('selectionId')
|
||||
name = sel.get('Name') or sel.get('name')
|
||||
print(f" - {name} (ID: {sid})")
|
||||
else:
|
||||
print("⚠️ No selections found with name 'Roboplanet'.")
|
||||
|
||||
# 2. Get Associate Mapping via Archive Provider
|
||||
# This avoids the Associate/{id} 500 error
|
||||
print("\n👥 Fetching Associate-to-Group mapping via Archive...")
|
||||
# Provider 'associate' is standard
|
||||
endpoint = "Archive/dynamic?provider=associate&columns=associateId,name,groupIdx"
|
||||
try:
|
||||
mapping_data = client._get(endpoint)
|
||||
if mapping_data and isinstance(mapping_data, list):
|
||||
print(f"✅ Received {len(mapping_data)} associate records.")
|
||||
robo_user_ids = []
|
||||
for item in mapping_data:
|
||||
aid = item.get("associateId")
|
||||
name = item.get("name")
|
||||
gid = item.get("groupIdx")
|
||||
|
||||
if gid == 52:
|
||||
print(f" - [ROBO] {name} (ID: {aid}, Group: {gid})")
|
||||
robo_user_ids.append(aid)
|
||||
elif "Fottner" in str(name) or aid == 321:
|
||||
print(f" - [EXCLUDE] {name} (ID: {aid}, Group: {gid})")
|
||||
|
||||
print(f"\n🚀 Identified {len(robo_user_ids)} Roboplanet Users.")
|
||||
if robo_user_ids:
|
||||
print(f"List of IDs: {robo_user_ids}")
|
||||
else:
|
||||
print("❌ Archive query returned no associate mapping.")
|
||||
except Exception as e:
|
||||
print(f"❌ Archive query failed: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_discovery()
|
||||
40
connector-superoffice/tools/cleanup_test_data.py
Normal file
40
connector-superoffice/tools/cleanup_test_data.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import sys
|
||||
import os
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Ensure we use the correct config and client
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 'connector-superoffice')))
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
def cleanup():
|
||||
print("🧹 Cleaning up Test Data...")
|
||||
client = SuperOfficeClient()
|
||||
|
||||
if not client.access_token:
|
||||
print("❌ Auth failed.")
|
||||
return
|
||||
|
||||
# Objects to delete (Reverse order of dependency)
|
||||
to_delete = [
|
||||
("Sale", 342539),
|
||||
("Appointment", 993350),
|
||||
("Appointment", 993347),
|
||||
("Person", 193092),
|
||||
("Contact", 171185) # Attempting to delete the company too
|
||||
]
|
||||
|
||||
for entity_type, entity_id in to_delete:
|
||||
print(f"🗑️ Deleting {entity_type} {entity_id}...")
|
||||
try:
|
||||
# SuperOffice DELETE usually returns 204 No Content
|
||||
# Our client returns None on success if response body is empty, or the JSON if not.
|
||||
# We need to catch exceptions if it fails.
|
||||
resp = client._delete(f"{entity_type}/{entity_id}")
|
||||
print(f"✅ Deleted {entity_type} {entity_id}")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Failed to delete {entity_type} {entity_id}: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
cleanup()
|
||||
69
connector-superoffice/tools/count_roboplanet_total.py
Normal file
69
connector-superoffice/tools/count_roboplanet_total.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# Absolute path setup
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
connector_dir = os.path.abspath(os.path.join(current_dir, '..'))
|
||||
sys.path.insert(0, connector_dir)
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
from config import settings
|
||||
|
||||
def verify_total_counts():
|
||||
print("📊 Verifying Global Account Counts...")
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Auth failed.")
|
||||
return
|
||||
|
||||
whitelist = settings.ROBOPLANET_WHITELIST
|
||||
|
||||
# 1. Try to get MemberCount from the Selection 10960 directly
|
||||
print("\n📁 Checking Selection 10960 (Alle_Contacts_Roboplanet)...")
|
||||
try:
|
||||
sel_details = client._get("Selection/10960")
|
||||
if sel_details:
|
||||
# Note: MemberCount is often a property of the Selection entity
|
||||
count = sel_details.get("MemberCount")
|
||||
print(f" 🔹 Web-Interface-equivalent Count (MemberCount): {count}")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Could not fetch Selection count property: {e}")
|
||||
|
||||
# 2. Manual Aggregate Count via OData
|
||||
# We construct a filter for all our IDs and Shortnames
|
||||
# This might be too long for a URL, so we do it in smaller batches if needed
|
||||
print("\n📡 Calculating Netto Count for Whitelist (IDs + Names)...")
|
||||
|
||||
# Divide whitelist into IDs and Names
|
||||
ids = [x for x in whitelist if isinstance(x, int)]
|
||||
names = [x for x in whitelist if isinstance(x, str)]
|
||||
|
||||
# Construct OData filter string
|
||||
# example: (associateId eq 528 or associateId eq 485 or associateId eq 'RKAB')
|
||||
id_filters = [f"associateId eq {i}" for i in ids]
|
||||
name_filters = [f"associateId eq '{n}'" for n in names]
|
||||
full_filter = " or ".join(id_filters + name_filters)
|
||||
|
||||
# We use $top=0 and $count=true to get JUST the number
|
||||
endpoint = f"Contact?$filter={full_filter}&$top=0&$count=true"
|
||||
|
||||
try:
|
||||
# Note: If the URL is too long (> 2000 chars), this might fail.
|
||||
# But for ~60 entries it should be fine.
|
||||
resp = client._get(endpoint)
|
||||
total_api_count = resp.get("@odata.count")
|
||||
print(f" 🎯 API Calculated Count (Whitelist-Match): {total_api_count}")
|
||||
|
||||
if total_api_count is not None:
|
||||
print(f"\n✅ PROOF: The API identifies {total_api_count} accounts for Roboplanet.")
|
||||
print("👉 Bitte vergleiche diese Zahl mit der Selektion 'Alle_Contacts_Roboplanet' im SuperOffice Web-Interface.")
|
||||
else:
|
||||
print("❌ API did not return a count property.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ OData Aggregation failed: {e}")
|
||||
print(" The filter string might be too long for the API.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
verify_total_counts()
|
||||
57
connector-superoffice/tools/create_company.py
Normal file
57
connector-superoffice/tools/create_company.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import sys
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Explicitly load .env from the parent directory
|
||||
dotenv_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '.env'))
|
||||
print(f"Loading .env from: {dotenv_path}")
|
||||
load_dotenv(dotenv_path=dotenv_path, override=True)
|
||||
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
from superoffice_client import SuperOfficeClient
|
||||
def create_test_company():
|
||||
"""
|
||||
Creates a new company in SuperOffice for E2E testing.
|
||||
"""
|
||||
company_name = "Bremer Abenteuerland"
|
||||
# Provide a real-world, scrapable website to test enrichment
|
||||
website = "https://www.belantis.de/"
|
||||
print(f"🚀 Attempting to create company: '{company_name}'")
|
||||
try:
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Authentication failed. Check your .env file.")
|
||||
return
|
||||
# Check if company already exists
|
||||
existing = client.search(f"Contact?$select=contactId,name&$filter=name eq '{company_name}'")
|
||||
print(f"DEBUG: Raw search response: {existing}")
|
||||
if existing:
|
||||
contact_id = existing[0]['contactId']
|
||||
print(f"⚠️ Company '{company_name}' already exists with ContactId: {contact_id}.")
|
||||
print("Skipping creation.")
|
||||
return contact_id
|
||||
payload = {
|
||||
"Name": company_name,
|
||||
"Urls": [
|
||||
{
|
||||
"Value": website,
|
||||
"Description": "Main Website"
|
||||
}
|
||||
],
|
||||
"Country": {
|
||||
"CountryId": 68 # Germany
|
||||
}
|
||||
}
|
||||
new_company = client._post("Contact", payload)
|
||||
if new_company and "contactId" in new_company:
|
||||
contact_id = new_company["contactId"]
|
||||
print(f"✅ SUCCESS! Created company '{company_name}' with ContactId: {contact_id}")
|
||||
return contact_id
|
||||
else:
|
||||
print(f"❌ Failed to create company. Response: {new_company}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
return None
|
||||
if __name__ == "__main__":
|
||||
create_test_company()
|
||||
44
connector-superoffice/tools/create_person_test.py
Normal file
44
connector-superoffice/tools/create_person_test.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
def create_test_person(contact_id: int):
|
||||
"""
|
||||
Creates a new person for a given contact ID.
|
||||
"""
|
||||
print(f"🚀 Attempting to create a person for Contact ID: {contact_id}")
|
||||
try:
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Authentication failed.")
|
||||
return
|
||||
|
||||
payload = {
|
||||
"Contact": {"ContactId": contact_id},
|
||||
"Firstname": "Test",
|
||||
"Lastname": "Person",
|
||||
"Emails": [
|
||||
{
|
||||
"Value": "floke.com@gmail.com",
|
||||
"Description": "Work Email"
|
||||
}
|
||||
]
|
||||
}
|
||||
new_person = client._post("Person", payload)
|
||||
if new_person and "PersonId" in new_person:
|
||||
person_id = new_person["PersonId"]
|
||||
print(f"✅ SUCCESS! Created person with PersonId: {person_id}")
|
||||
return person_id
|
||||
else:
|
||||
print(f"❌ Failed to create person. Response: {new_person}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
return None
|
||||
|
||||
if __name__ == "__main__":
|
||||
TEST_CONTACT_ID = 171185
|
||||
if len(sys.argv) > 1:
|
||||
TEST_CONTACT_ID = int(sys.argv[1])
|
||||
create_test_person(TEST_CONTACT_ID)
|
||||
40
connector-superoffice/tools/debug_config_types.py
Normal file
40
connector-superoffice/tools/debug_config_types.py
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
import sys
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Explicitly load .env from the project root
|
||||
dotenv_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '.env'))
|
||||
print(f"Loading .env from: {dotenv_path}")
|
||||
load_dotenv(dotenv_path=dotenv_path, override=True)
|
||||
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
from config import settings
|
||||
|
||||
print("\n--- DEBUGGING CONFIG TYPES ---")
|
||||
try:
|
||||
print(f"UDF_VERTICAL: {settings.UDF_VERTICAL} (Type: {type(settings.UDF_VERTICAL)})")
|
||||
print(f"UDF_SUMMARY: {settings.UDF_SUMMARY} (Type: {type(settings.UDF_SUMMARY)})")
|
||||
print(f"UDF_OPENER: {settings.UDF_OPENER} (Type: {type(settings.UDF_OPENER)})")
|
||||
print(f"UDF_OPENER_SECONDARY: {settings.UDF_OPENER_SECONDARY} (Type: {type(settings.UDF_OPENER_SECONDARY)})")
|
||||
print(f"UDF_LAST_UPDATE: {settings.UDF_LAST_UPDATE} (Type: {type(settings.UDF_LAST_UPDATE)})")
|
||||
print(f"UDF_LAST_OUTREACH: {settings.UDF_LAST_OUTREACH} (Type: {type(settings.UDF_LAST_OUTREACH)})")
|
||||
|
||||
# Test dictionary creation to force the error if a key is a dict
|
||||
print("\nAttempting to create dictionary with these keys...")
|
||||
test_dict = {
|
||||
settings.UDF_VERTICAL: "Vertical",
|
||||
settings.UDF_SUMMARY: "Summary",
|
||||
settings.UDF_OPENER: "Opener",
|
||||
settings.UDF_OPENER_SECONDARY: "Opener 2",
|
||||
settings.UDF_LAST_UPDATE: "Last Update",
|
||||
settings.UDF_LAST_OUTREACH: "Last Outreach"
|
||||
}
|
||||
print("✅ Dictionary creation SUCCESSFUL.")
|
||||
|
||||
except TypeError as e:
|
||||
print(f"\n❌ TypeError CAUGHT: {e}")
|
||||
print("One of the settings above is likely a dictionary or unhashable type!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Unknown Error: {e}")
|
||||
32
connector-superoffice/tools/debug_env_types.py
Normal file
32
connector-superoffice/tools/debug_env_types.py
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Explicitly load .env from the project root
|
||||
dotenv_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '.env'))
|
||||
print(f"Loading .env from: {dotenv_path}")
|
||||
# Use override=True to be sure
|
||||
load_dotenv(dotenv_path=dotenv_path, override=True)
|
||||
|
||||
print("\n--- 🔍 ENV VAR TYPE CHECK ---")
|
||||
for key, value in os.environ.items():
|
||||
if key.startswith("UDF_") or key.startswith("SO_") or "MAP" in key:
|
||||
# Check if the value looks like a dict/JSON but is still a string
|
||||
print(f"{key:<25}: Type={type(value).__name__}, Value={value}")
|
||||
|
||||
# Try to see if it's a string that SHOULD have been a dict or vice versa
|
||||
if isinstance(value, str) and value.startswith("{"):
|
||||
print(f" ⚠️ ALERT: String looks like JSON!")
|
||||
|
||||
print("\n--- ⚙️ SETTINGS OBJECT CHECK ---")
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
from config import settings
|
||||
|
||||
for attr in dir(settings):
|
||||
if attr.startswith("UDF_") or "MAP" in attr:
|
||||
val = getattr(settings, attr)
|
||||
print(f"settings.{attr:<20}: Type={type(val).__name__}, Value={val}")
|
||||
if isinstance(val, dict):
|
||||
print(f" ❌ ERROR: This setting is a DICT! This will crash dictionary lookups.")
|
||||
|
||||
print("-----------------------------")
|
||||
35
connector-superoffice/tools/debug_names.py
Normal file
35
connector-superoffice/tools/debug_names.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# Absolute path setup
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
connector_dir = os.path.abspath(os.path.join(current_dir, '..'))
|
||||
sys.path.insert(0, connector_dir)
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
def debug_names():
|
||||
print("🔎 Debugging Associate Names...")
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Auth failed.")
|
||||
return
|
||||
|
||||
endpoint = "Contact?$orderby=contactId desc&$top=5&$select=name,Associate/Name"
|
||||
|
||||
print(f"📡 Querying: {endpoint}")
|
||||
contacts = client.search(endpoint)
|
||||
|
||||
if contacts:
|
||||
for c in contacts:
|
||||
cname = c.get('name')
|
||||
assoc = c.get('Associate') or {}
|
||||
aname = assoc.get('Name')
|
||||
print(f" 🏢 Contact: {cname}")
|
||||
print(f" 👉 Associate Name: '{aname}'")
|
||||
else:
|
||||
print("❌ No contacts found.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
debug_names()
|
||||
45
connector-superoffice/tools/debug_raw_response.py
Normal file
45
connector-superoffice/tools/debug_raw_response.py
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
import sys
|
||||
import os
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Explicitly load .env from the project root
|
||||
dotenv_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '.env'))
|
||||
load_dotenv(dotenv_path=dotenv_path, override=True)
|
||||
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
def get_raw_data(contact_id: int):
|
||||
print(f"🚀 Fetching RAW response for ContactId: {contact_id}")
|
||||
try:
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Authentication failed.")
|
||||
return
|
||||
|
||||
# Build URL manually to avoid any JSON parsing in the client
|
||||
url = f"{client.base_url}/Contact/{contact_id}?$select=Name,UserDefinedFields"
|
||||
headers = client.headers
|
||||
|
||||
print(f"URL: {url}")
|
||||
resp = requests.get(url, headers=headers)
|
||||
|
||||
print(f"Status Code: {resp.status_code}")
|
||||
|
||||
# Save raw content to a file
|
||||
output_file = "raw_api_response.json"
|
||||
with open(output_file, "w") as f:
|
||||
f.write(resp.text)
|
||||
|
||||
print(f"✅ Raw response saved to {output_file}")
|
||||
print("\nFirst 500 characters of response:")
|
||||
print(resp.text[:500])
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
get_raw_data(171185)
|
||||
|
||||
66
connector-superoffice/tools/discover_associates.py
Normal file
66
connector-superoffice/tools/discover_associates.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# Absolute path setup to avoid import errors
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
connector_dir = os.path.abspath(os.path.join(current_dir, '..'))
|
||||
sys.path.insert(0, connector_dir)
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
def discover_associates_and_groups():
|
||||
print("🔎 Discovering Associates and Groups...")
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Auth failed.")
|
||||
return
|
||||
|
||||
# 1. Fetch User Groups
|
||||
print("\n👥 Fetching User Groups...")
|
||||
groups = client._get("MDOList/usergroup")
|
||||
|
||||
robo_group_id = None
|
||||
|
||||
if groups:
|
||||
for group in groups:
|
||||
name = group.get('Name')
|
||||
grp_id = group.get('Id')
|
||||
print(f" - Group: {name} (ID: {grp_id})")
|
||||
if "Roboplanet" in name:
|
||||
robo_group_id = grp_id
|
||||
|
||||
if robo_group_id:
|
||||
print(f"✅ Identified Roboplanet Group ID: {robo_group_id}")
|
||||
else:
|
||||
print("⚠️ Could not auto-identify Roboplanet group. Check the list above.")
|
||||
|
||||
# 2. Check Candidate IDs directly
|
||||
print("\n👤 Checking specific Person IDs for Willi Fottner...")
|
||||
candidates = [6, 182552]
|
||||
|
||||
for pid in candidates:
|
||||
try:
|
||||
p = client.get_person(pid)
|
||||
if p:
|
||||
fname = p.get('Firstname')
|
||||
lname = p.get('Lastname')
|
||||
is_assoc = p.get('IsAssociate')
|
||||
|
||||
print(f" 👉 Person {pid}: {fname} {lname} (IsAssociate: {is_assoc})")
|
||||
|
||||
if is_assoc:
|
||||
assoc_obj = p.get("Associate")
|
||||
if assoc_obj:
|
||||
assoc_id = assoc_obj.get("AssociateId")
|
||||
grp = assoc_obj.get("GroupIdx")
|
||||
print(f" ✅ IS ASSOCIATE! ID: {assoc_id}, Group: {grp}")
|
||||
if "Fottner" in str(lname) or "Willi" in str(fname):
|
||||
print(f" 🎯 TARGET IDENTIFIED: Willi Fottner is Associate ID {assoc_id}")
|
||||
except Exception as e:
|
||||
print(f" ❌ Error checking Person {pid}: {e}")
|
||||
|
||||
print("\n--- Done ---")
|
||||
|
||||
if __name__ == "__main__":
|
||||
discover_associates_and_groups()
|
||||
72
connector-superoffice/tools/final_mailing_test.py
Normal file
72
connector-superoffice/tools/final_mailing_test.py
Normal file
@@ -0,0 +1,72 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
from datetime import datetime, timedelta
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Ensure we use the correct config and client from the connector-superoffice subdir
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 'connector-superoffice')))
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
def run_final_test():
|
||||
print("🚀 Starting Final Mailing Test for floke.com@gmail.com...")
|
||||
|
||||
# 1. Initialize Client
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Auth failed.")
|
||||
return
|
||||
|
||||
# 2. Use Target Contact (Bremer Abenteuerland)
|
||||
contact_id = 171185
|
||||
print(f"✅ Using Contact ID: {contact_id}")
|
||||
|
||||
# 3. Use Created Person
|
||||
person_id = 193092
|
||||
print(f"✅ Using Person ID: {person_id} (floke.com@gmail.com)")
|
||||
|
||||
# 4. Attempt Shipment (Mailing)
|
||||
print("📤 Attempting to create Shipment (the direct email send)...")
|
||||
shipment_payload = {
|
||||
"Name": "Gemini Diagnostics: Test Shipment",
|
||||
"Subject": "Hallo aus der Gemini GTM Engine",
|
||||
"Body": "Dies ist ein Testversuch für den direkten E-Mail-Versand via SuperOffice API.",
|
||||
"DocumentTemplateId": 157, # Outgoing Email (ID 157 is confirmed from previous runs as typical)
|
||||
"ShipmentType": "Email",
|
||||
"AssociateId": 528, # API User RCGO
|
||||
"ContactId": contact_id,
|
||||
"PersonId": person_id,
|
||||
"Status": "Ready"
|
||||
}
|
||||
|
||||
try:
|
||||
shipment_resp = client._post("Shipment", shipment_payload)
|
||||
if shipment_resp:
|
||||
print("✅ UNEXPECTED SUCCESS: Shipment created!")
|
||||
print(json.dumps(shipment_resp, indent=2))
|
||||
else:
|
||||
print("❌ Shipment creation returned empty response.")
|
||||
except Exception as e:
|
||||
print(f"❌ EXPECTED FAILURE: Shipment creation failed as predicted.")
|
||||
print(f"Error details: {e}")
|
||||
|
||||
# 5. Fallback: Create Appointment as "Proof of Work"
|
||||
print("\n📅 Running Workaround: Creating Appointment instead...")
|
||||
appt_resp = client.create_appointment(
|
||||
subject="KI: E-Mail Testversuch an floke.com@gmail.com",
|
||||
description="Hier würde der E-Mail-Text stehen, der aufgrund technischer Blockaden (Mailing-Modul/Identität) nicht direkt versendet werden konnte.",
|
||||
contact_id=contact_id,
|
||||
person_id=person_id
|
||||
)
|
||||
|
||||
if appt_resp:
|
||||
appt_id = appt_resp.get("appointmentId") or appt_resp.get("AppointmentId")
|
||||
print(f"✅ Workaround Successful: Appointment ID: {appt_id}")
|
||||
print(f"🔗 Link: https://online3.superoffice.com/Cust26720/default.aspx?appointment_id={appt_id}")
|
||||
else:
|
||||
print("❌ Workaround (Appointment) failed too.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_final_test()
|
||||
65
connector-superoffice/tools/final_truth_check.py
Normal file
65
connector-superoffice/tools/final_truth_check.py
Normal file
@@ -0,0 +1,65 @@
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
def check_truth():
|
||||
file_path = "raw_api_response.json"
|
||||
if not os.path.exists(file_path):
|
||||
print(f"❌ Datei {file_path} nicht gefunden.")
|
||||
return
|
||||
|
||||
print(f"🧐 Analysiere {file_path}...")
|
||||
try:
|
||||
with open(file_path, "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
udfs = data.get("UserDefinedFields", {})
|
||||
print(f"✅ JSON erfolgreich geladen. UDFs gefunden: {len(udfs)}")
|
||||
|
||||
invalid_keys = []
|
||||
for key in udfs.keys():
|
||||
if not isinstance(key, str):
|
||||
invalid_keys.append((key, type(key)))
|
||||
|
||||
if invalid_keys:
|
||||
print(f"❌ FEHLER GEFUNDEN! Folgende Keys sind KEINE Strings:")
|
||||
for k, t in invalid_keys:
|
||||
print(f" - Key: {k}, Typ: {t}")
|
||||
else:
|
||||
print("✅ Alle Keys in UserDefinedFields sind valide Strings.")
|
||||
|
||||
# Jetzt prüfen wir unsere eigenen Settings gegen dieses Dict
|
||||
print("\n--- Teste Zugriff mit unseren Settings ---")
|
||||
from dotenv import load_dotenv
|
||||
import sys
|
||||
|
||||
# Pfad zum Hauptverzeichnis für .env
|
||||
dotenv_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '.env'))
|
||||
load_dotenv(dotenv_path=dotenv_path, override=True)
|
||||
|
||||
# Pfad für config import
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
from config import settings
|
||||
|
||||
test_keys = {
|
||||
"Vertical": settings.UDF_VERTICAL,
|
||||
"Summary": settings.UDF_SUMMARY,
|
||||
"Opener": settings.UDF_OPENER
|
||||
}
|
||||
|
||||
for name, key in test_keys.items():
|
||||
print(f"Prüfe {name} (Key: '{key}', Typ: {type(key)})...")
|
||||
try:
|
||||
# Das ist die Stelle, die vorhin zum Absturz führte
|
||||
val = udfs.get(key)
|
||||
print(f" -> Zugriff erfolgreich! Wert: {val}")
|
||||
except TypeError as e:
|
||||
print(f" -> ❌ ABSTURZ: {e}")
|
||||
if isinstance(key, dict):
|
||||
print(f" Grund: settings.UDF_{name.upper()} ist ein DICTIONARY!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Allgemeiner Fehler bei der Analyse: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_truth()
|
||||
66
connector-superoffice/tools/final_vertical_discovery.py
Normal file
66
connector-superoffice/tools/final_vertical_discovery.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# Absolute path to the connector-superoffice directory
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
connector_dir = os.path.abspath(os.path.join(current_dir, '..'))
|
||||
|
||||
# CRITICAL: Insert at 0 to shadow /app/config.py
|
||||
sys.path.insert(0, connector_dir)
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
def discover_verticals():
|
||||
print("🔎 Starting Final Vertical Discovery (Production)...")
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Auth failed.")
|
||||
return
|
||||
|
||||
# 1. Fetch Contact UDF Layout to find the List ID behind SuperOffice:83
|
||||
print("📡 Fetching Contact UDF Layout (Metadata)...")
|
||||
layout = client._get("Contact/UdefLayout/Published")
|
||||
|
||||
list_id = None
|
||||
if layout and 'Fields' in layout:
|
||||
for field in layout['Fields']:
|
||||
if field.get('ProgId') == 'SuperOffice:83':
|
||||
print(f"✅ Found SuperOffice:83: {field.get('Label')}")
|
||||
list_id = field.get('ListId')
|
||||
print(f"✅ List ID: {list_id}")
|
||||
break
|
||||
|
||||
if not list_id:
|
||||
print("❌ Could not find Metadata for SuperOffice:83.")
|
||||
return
|
||||
|
||||
# 2. Fetch the List Items for this List
|
||||
print(f"📡 Fetching List Items for List ID {list_id}...")
|
||||
# List endpoint is typically List/ListId/Items
|
||||
# Let's try to get all rows for this list
|
||||
items = client._get(f"List/{list_id}/Items")
|
||||
|
||||
if items:
|
||||
print(f"✅ SUCCESS! Found {len(items)} items in the Vertical list.")
|
||||
mapping = {}
|
||||
for item in items:
|
||||
name = item.get('Value') or item.get('Name')
|
||||
item_id = item.get('Id')
|
||||
mapping[name] = item_id
|
||||
print(f" - {name}: {item_id}")
|
||||
|
||||
print("\n🚀 FINAL MAPPING JSON (Copy to .env VERTICAL_MAP_JSON):")
|
||||
print(json.dumps(mapping))
|
||||
else:
|
||||
print(f"❌ Could not fetch items for List {list_id}. Trying MDO List...")
|
||||
# Fallback to MDO List
|
||||
mdo_items = client._get(f"MDOList/udlist{list_id}")
|
||||
if mdo_items:
|
||||
print("✅ Success via MDO List.")
|
||||
# ... process mdo items if needed ...
|
||||
else:
|
||||
print("❌ MDO List fallback failed too.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
discover_verticals()
|
||||
@@ -0,0 +1,41 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# Absolute path setup
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
connector_dir = os.path.abspath(os.path.join(current_dir, '..'))
|
||||
sys.path.insert(0, connector_dir)
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
def find_latest_roboplanet():
|
||||
print("🔎 Searching for the latest Roboplanet (Group 52) Account...")
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Auth failed.")
|
||||
return
|
||||
|
||||
# DIAGNOSTIC: Search for Account of Associate 528 (RCGO)
|
||||
endpoint = "Contact?$filter=associateId eq 528&$orderby=contactId desc&$top=1&$select=contactId,name,Associate"
|
||||
|
||||
print(f"📡 Diagnostic Query: {endpoint}")
|
||||
|
||||
try:
|
||||
results = client.search(endpoint)
|
||||
|
||||
if results and len(results) > 0:
|
||||
contact = results[0]
|
||||
print("\n✅ FOUND ACCOUNT FOR RCGO (528):")
|
||||
print(json.dumps(contact, indent=2))
|
||||
|
||||
# Check GroupIdx
|
||||
# Usually flat like "Associate": {"GroupIdx": 52...}
|
||||
else:
|
||||
print("\n❌ NO ACCOUNTS FOUND for RCGO (528).")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
find_latest_roboplanet()
|
||||
60
connector-superoffice/tools/find_missing_whitelist_ids.py
Normal file
60
connector-superoffice/tools/find_missing_whitelist_ids.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# Absolute path setup
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
connector_dir = os.path.abspath(os.path.join(current_dir, '..'))
|
||||
sys.path.insert(0, connector_dir)
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
from config import settings
|
||||
|
||||
def find_missing():
|
||||
print("🔎 Scanning for Associate IDs not in Whitelist...")
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Auth failed.")
|
||||
return
|
||||
|
||||
whitelist = settings.ROBOPLANET_WHITELIST
|
||||
|
||||
# Fetch 500 contacts
|
||||
limit = 500
|
||||
endpoint = f"Contact?$orderby=contactId desc&$top={limit}&$select=associateId"
|
||||
|
||||
print(f"📡 Scanning {limit} records...")
|
||||
contacts = client.search(endpoint)
|
||||
|
||||
if contacts:
|
||||
missing_ids = set()
|
||||
match_count = 0
|
||||
for c in contacts:
|
||||
aid = c.get('associateId') or c.get('AssociateId')
|
||||
if aid:
|
||||
is_match = False
|
||||
if str(aid).upper() in whitelist: is_match = True
|
||||
try:
|
||||
if int(aid) in whitelist: is_match = True
|
||||
except: pass
|
||||
|
||||
if is_match:
|
||||
match_count += 1
|
||||
else:
|
||||
missing_ids.add(aid)
|
||||
|
||||
print(f"\n📊 Scan Results ({limit} records):")
|
||||
print(f" - Total Matches (Roboplanet): {match_count}")
|
||||
print(f" - Missing/Other IDs: {len(missing_ids)}")
|
||||
|
||||
if missing_ids:
|
||||
print("\n✅ Found IDs NOT in whitelist:")
|
||||
for mid in sorted(list(missing_ids), key=lambda x: str(x)):
|
||||
print(f" - {mid}")
|
||||
|
||||
print("\n👉 Bitte prüfe, ob eine dieser IDs ebenfalls zu Roboplanet gehört.")
|
||||
else:
|
||||
print("❌ No contacts found.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
find_missing()
|
||||
54
connector-superoffice/tools/full_discovery.py
Normal file
54
connector-superoffice/tools/full_discovery.py
Normal file
@@ -0,0 +1,54 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Explicitly load .env
|
||||
dotenv_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '.env'))
|
||||
load_dotenv(dotenv_path=dotenv_path, override=True)
|
||||
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
def run_discovery():
|
||||
print(f"🚀 Running Full Discovery on PRODUCTION...")
|
||||
try:
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token: return
|
||||
|
||||
# 1. Check Contact UDFs
|
||||
print("\n--- 🏢 CONTACT UDFs (ProgIDs) ---")
|
||||
contact_meta = client._get("Contact/default")
|
||||
if contact_meta and 'UserDefinedFields' in contact_meta:
|
||||
udfs = contact_meta['UserDefinedFields']
|
||||
for key in sorted(udfs.keys()):
|
||||
print(f" - {key}")
|
||||
|
||||
# 2. Check Person UDFs
|
||||
print("\n--- 👤 PERSON UDFs (ProgIDs) ---")
|
||||
person_meta = client._get("Person/default")
|
||||
if person_meta and 'UserDefinedFields' in person_meta:
|
||||
udfs = person_meta['UserDefinedFields']
|
||||
for key in sorted(udfs.keys()):
|
||||
print(f" - {key}")
|
||||
|
||||
# 3. Check specific List IDs (e.g. Verticals)
|
||||
# This often requires admin rights to see all list definitions
|
||||
print("\n--- 📋 LIST CHECK (Verticals) ---")
|
||||
# Assuming udlist331 is the list for Verticals (based on previous logs)
|
||||
list_data = client._get("List/udlist331/Items")
|
||||
if list_data and 'value' in list_data:
|
||||
print(f"Found {len(list_data['value'])} items in Vertical list.")
|
||||
for item in list_data['value'][:5]:
|
||||
print(f" - ID {item['Id']}: {item['Name']}")
|
||||
else:
|
||||
print(" ⚠️ Could not access Vertical list items.")
|
||||
|
||||
print("\n✅ Discovery Complete.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_discovery()
|
||||
84
connector-superoffice/tools/get_enriched_company_data.py
Normal file
84
connector-superoffice/tools/get_enriched_company_data.py
Normal file
@@ -0,0 +1,84 @@
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import traceback
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Explicitly load .env from the project root
|
||||
dotenv_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '.env'))
|
||||
load_dotenv(dotenv_path=dotenv_path, override=True)
|
||||
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
from superoffice_client import SuperOfficeClient
|
||||
from config import settings
|
||||
|
||||
def get_enriched_data(contact_id: int):
|
||||
print(f"🚀 [DEBUG] Starting fetch for ContactId: {contact_id}")
|
||||
try:
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Authentication failed.")
|
||||
return
|
||||
|
||||
print("✅ [DEBUG] Client authenticated.")
|
||||
|
||||
try:
|
||||
contact_data = client.get_contact(
|
||||
contact_id,
|
||||
select=[
|
||||
"Name", "UrlAddress", "OrgNr", "UserDefinedFields"
|
||||
]
|
||||
)
|
||||
print(f"✅ [DEBUG] API Call successful. Data type: {type(contact_data)}")
|
||||
except Exception as e:
|
||||
print(f"❌ [DEBUG] API Call failed: {e}")
|
||||
traceback.print_exc()
|
||||
return
|
||||
|
||||
if not contact_data:
|
||||
print("❌ [DEBUG] No data returned.")
|
||||
return
|
||||
|
||||
print(f"✅ [DEBUG] Name: {contact_data.get('Name')}")
|
||||
|
||||
try:
|
||||
udfs = contact_data.get("UserDefinedFields", {})
|
||||
print(f"✅ [DEBUG] UDFs extracted. Type: {type(udfs)}")
|
||||
except Exception as e:
|
||||
print(f"❌ [DEBUG] Failed to extract UDFs: {e}")
|
||||
traceback.print_exc()
|
||||
return
|
||||
|
||||
if isinstance(udfs, dict):
|
||||
print(f"✅ [DEBUG] UDFs is a dict with {len(udfs)} keys.")
|
||||
try:
|
||||
# Iterate keys safely
|
||||
print("--- UDF KEYS SAMPLE ---")
|
||||
for k in list(udfs.keys())[:5]:
|
||||
print(f"Key: {k} (Type: {type(k)})")
|
||||
except Exception as e:
|
||||
print(f"❌ [DEBUG] Failed to iterate keys: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
# Try to access specific key
|
||||
target_key = settings.UDF_VERTICAL
|
||||
print(f"✅ [DEBUG] Attempting access with key: '{target_key}' (Type: {type(target_key)})")
|
||||
|
||||
try:
|
||||
if target_key in udfs:
|
||||
val = udfs[target_key]
|
||||
print(f"✅ [DEBUG] Value found: {val}")
|
||||
else:
|
||||
print(f"ℹ️ [DEBUG] Key not found in UDFs.")
|
||||
except Exception as e:
|
||||
print(f"❌ [DEBUG] Failed to access dictionary with key: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ [DEBUG] Global Error: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
if __name__ == "__main__":
|
||||
target_contact_id = 171185
|
||||
get_enriched_data(target_contact_id)
|
||||
98
connector-superoffice/tools/inspect_group_users.py
Normal file
98
connector-superoffice/tools/inspect_group_users.py
Normal file
@@ -0,0 +1,98 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# Absolute path setup
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
connector_dir = os.path.abspath(os.path.join(current_dir, '..'))
|
||||
sys.path.insert(0, connector_dir)
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
def inspect_group():
|
||||
print("🔎 Inspecting Group 52 (Roboplanet)...")
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Auth failed.")
|
||||
return
|
||||
|
||||
# 1. Find Users in Group 52
|
||||
print("\n👥 Finding Associates in Group 52...")
|
||||
associates = client._get("MDOList/associate")
|
||||
|
||||
robo_associates = []
|
||||
if associates:
|
||||
for assoc in associates:
|
||||
# Note: MDOList returns flat items.
|
||||
# We might need to fetch details or check 'GroupIdx' if present in ExtraInfo
|
||||
# Let's check keys first
|
||||
# print(assoc.keys())
|
||||
|
||||
# The 'GroupIdx' is usually in 'ExtraInfo' or needs detail fetch
|
||||
# But earlier discovery showed 'GroupIdx' directly? No, I inferred it.
|
||||
# Let's fetch details for a few to be sure.
|
||||
assoc_id = assoc.get('Id')
|
||||
|
||||
# Optimization: Only check first 50 to avoid spam, or check by Name if we know one
|
||||
# Better: Use OData to filter associates by group?
|
||||
# "Associate?$filter=groupIdx eq 52" -> Let's try this first!
|
||||
pass
|
||||
|
||||
# Efficient OData Search for Associates in Group 52
|
||||
users_in_group = client.search("Associate?$filter=groupIdx eq 52")
|
||||
|
||||
if users_in_group:
|
||||
print(f"✅ Found {len(users_in_group)} Associates in Group 52:")
|
||||
for u in users_in_group:
|
||||
uid = u.get('associateId') or u.get('AssociateId')
|
||||
name = u.get('name') or u.get('Name') or u.get('fullName')
|
||||
print(f" - {name} (ID: {uid})")
|
||||
robo_associates.append(uid)
|
||||
else:
|
||||
print("⚠️ No Associates found in Group 52 via OData.")
|
||||
print(" Trying manual scan of MDOList (slower)...")
|
||||
# Fallback loop
|
||||
if associates:
|
||||
count = 0
|
||||
for assoc in associates:
|
||||
aid = assoc.get('Id')
|
||||
det = client._get(f"Associate/{aid}")
|
||||
if det and det.get('GroupIdx') == 52:
|
||||
print(f" - {det.get('Name')} (ID: {aid}) [via Detail]")
|
||||
robo_associates.append(aid)
|
||||
count += 1
|
||||
if count > 5:
|
||||
print(" ... (stopping scan)")
|
||||
break
|
||||
|
||||
if not robo_associates:
|
||||
print("❌ CRITICAL: Group 52 seems empty! Filter logic will block everything.")
|
||||
return
|
||||
|
||||
# 2. Check a Contact owned by one of these users
|
||||
test_user_id = robo_associates[0]
|
||||
print(f"\n🏢 Checking a Contact owned by User {test_user_id}...")
|
||||
|
||||
contacts = client.search(f"Contact?$filter=associateId eq {test_user_id}&$top=1&$select=ContactId,Name,Associate/GroupIdx")
|
||||
|
||||
if contacts:
|
||||
c = contacts[0]
|
||||
cid = c.get('contactId') or c.get('ContactId')
|
||||
cname = c.get('name') or c.get('Name')
|
||||
# Check nested Associate GroupIdx if returned, or fetch detail
|
||||
print(f" found: {cname} (ID: {cid})")
|
||||
|
||||
# Double Check with full Get
|
||||
full_c = client.get_contact(cid)
|
||||
assoc_grp = full_c.get('Associate', {}).get('GroupIdx')
|
||||
print(f" 👉 Contact Associate GroupIdx: {assoc_grp}")
|
||||
|
||||
if assoc_grp == 52:
|
||||
print("✅ VERIFIED: Filter logic 'GroupIdx == 52' will work.")
|
||||
else:
|
||||
print(f"❌ MISMATCH: Contact GroupIdx is {assoc_grp}, expected 52.")
|
||||
else:
|
||||
print("⚠️ User has no contacts. Cannot verify contact group mapping.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
inspect_group()
|
||||
73
connector-superoffice/tools/precise_count_verification.py
Normal file
73
connector-superoffice/tools/precise_count_verification.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# Absolute path setup
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
connector_dir = os.path.abspath(os.path.join(current_dir, '..'))
|
||||
sys.path.insert(0, connector_dir)
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
from config import settings
|
||||
|
||||
def run_precise_check():
|
||||
print("📊 Precise Count Verification: API vs. Whitelist...")
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Auth failed.")
|
||||
return
|
||||
|
||||
whitelist = settings.ROBOPLANET_WHITELIST
|
||||
ids_in_whitelist = [x for x in whitelist if isinstance(x, int)]
|
||||
|
||||
# 1. Individual Counts for our Whitelist IDs
|
||||
print(f"\n🔢 Counting accounts for the {len(ids_in_whitelist)} IDs in whitelist...")
|
||||
total_whitelist_count = 0
|
||||
for aid in ids_in_whitelist:
|
||||
endpoint = f"Contact?$filter=associateId eq {aid}&$top=0&$count=true"
|
||||
try:
|
||||
resp = client._get(endpoint)
|
||||
count = resp.get('@odata.count') or 0
|
||||
if count > 0:
|
||||
# print(f" - ID {aid}: {count}")
|
||||
total_whitelist_count += count
|
||||
except:
|
||||
pass
|
||||
|
||||
print(f"✅ Total accounts owned by Whitelist IDs: {total_whitelist_count}")
|
||||
|
||||
# 2. Check for "Strangers" in the Selection 10960
|
||||
# We want to find who else is in that selection
|
||||
print(f"\n🕵️ Looking for Owners in Selection 10960 who are NOT in our whitelist...")
|
||||
|
||||
# We use Archive/dynamic to group members by AssociateId
|
||||
# This is the most efficient way to see all owners in the selection
|
||||
endpoint = "Archive/dynamic?provider=selectionmember&columns=contact/associateId,contact/associate/name&criteria=selectionId=10960&$top=1000"
|
||||
|
||||
try:
|
||||
members = client._get(endpoint)
|
||||
if members and isinstance(members, list):
|
||||
owners_in_selection = {}
|
||||
for m in members:
|
||||
aid = m.get("contact/associateId")
|
||||
aname = m.get("contact/associate/name")
|
||||
if aid:
|
||||
owners_in_selection[aid] = aname
|
||||
|
||||
print(f"Found {len(owners_in_selection)} distinct owners in the first 1000 members of selection.")
|
||||
for aid, name in owners_in_selection.items():
|
||||
if aid not in whitelist and name not in whitelist:
|
||||
print(f" ⚠️ OWNER NOT IN WHITELIST: {name} (ID: {aid})")
|
||||
else:
|
||||
print("⚠️ Could not group selection members by owner via API.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Archive grouping failed: {e}")
|
||||
|
||||
print(f"\n🏁 Target from UI: 17014")
|
||||
print(f"🏁 Whitelist sum: {total_whitelist_count}")
|
||||
delta = 17014 - total_whitelist_count
|
||||
print(f"🏁 Delta: {delta}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_precise_check()
|
||||
68
connector-superoffice/tools/round_trip_final.py
Normal file
68
connector-superoffice/tools/round_trip_final.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Path gymnastics
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "connector-superoffice"))
|
||||
|
||||
from company_explorer_connector import get_company_details
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
# Load ENV
|
||||
load_dotenv(dotenv_path="/home/node/clawd/.env", override=True)
|
||||
|
||||
def perform_final_round_trip(ce_id):
|
||||
client = SuperOfficeClient()
|
||||
print(f"--- Final Round-Trip: CE {ce_id} -> SuperOffice ---")
|
||||
|
||||
# 1. Get enriched data from CE
|
||||
ce_data = get_company_details(ce_id)
|
||||
if not ce_data or "error" in ce_data:
|
||||
print("❌ Could not fetch CE data.")
|
||||
return
|
||||
|
||||
so_id = ce_data.get("crm_id")
|
||||
if not so_id:
|
||||
print("❌ No SO ID found in CE.")
|
||||
return
|
||||
|
||||
# 2. Fetch current SO contact
|
||||
contact = client._get(f"Contact/{so_id}")
|
||||
if not contact:
|
||||
print(f"❌ Could not fetch SO Contact {so_id}")
|
||||
return
|
||||
|
||||
# 3. Intelligent Mapping (Full Object)
|
||||
print(f"Mapping data for {ce_data.get('name')}...")
|
||||
|
||||
# Simple Fields
|
||||
contact["UrlAddress"] = ce_data.get("website", "")
|
||||
contact["Department"] = "KI-Enriched via CE"
|
||||
|
||||
# Address Object
|
||||
if "Address" not in contact: contact["Address"] = {}
|
||||
if "Street" not in contact["Address"]: contact["Address"]["Street"] = {}
|
||||
|
||||
contact["Address"]["Street"]["Address1"] = ce_data.get("address", "")
|
||||
contact["Address"]["Street"]["City"] = ce_data.get("city", "")
|
||||
contact["Address"]["Street"]["Zipcode"] = ce_data.get("zip", "")
|
||||
|
||||
# Phones (List)
|
||||
if ce_data.get("phone"):
|
||||
contact["Phones"] = [{"Number": ce_data.get("phone"), "Description": "Main"}]
|
||||
|
||||
# 4. Write back
|
||||
print(f"Sending full update to SO Contact {so_id}...")
|
||||
result = client._put(f"Contact/{so_id}", contact)
|
||||
|
||||
if result:
|
||||
print("🚀 SUCCESS! Round-trip for Robo-Planet complete.")
|
||||
print(f"Website: {contact['UrlAddress']}")
|
||||
print(f"City: {contact['Address']['Street']['City']}")
|
||||
else:
|
||||
print("❌ Update failed.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
perform_final_round_trip(53)
|
||||
111
connector-superoffice/tools/seed_test_data.py
Normal file
111
connector-superoffice/tools/seed_test_data.py
Normal file
@@ -0,0 +1,111 @@
|
||||
import sys
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
from sqlalchemy import create_engine, select
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
# Add paths to use backend models directly for complex seeding (Matrix/Person)
|
||||
sys.path.append(os.path.join(os.getcwd(), "company-explorer"))
|
||||
from backend.database import Base, Company, Contact, Industry, JobRoleMapping, MarketingMatrix
|
||||
|
||||
# Database Connection (Direct SQL access is easier for seeding specific IDs)
|
||||
DB_PATH = "sqlite:///companies_v3_fixed_2.db" # Local relative path
|
||||
engine = create_engine(DB_PATH)
|
||||
Session = sessionmaker(bind=engine)
|
||||
session = Session()
|
||||
|
||||
def seed():
|
||||
print("--- Company Explorer Test Data Seeder ---")
|
||||
print("This script prepares the database for the SuperOffice Connector End-to-End Test.")
|
||||
|
||||
# 1. User Input
|
||||
so_contact_id = input("Enter SuperOffice Contact ID (Company) [e.g. 123]: ").strip()
|
||||
so_person_id = input("Enter SuperOffice Person ID [e.g. 456]: ").strip()
|
||||
company_name = input("Enter Company Name [e.g. Test GmbH]: ").strip() or "Test GmbH"
|
||||
person_role = "Geschäftsführer" # Fixed for test simplicity
|
||||
industry_name = "Logistik" # Fixed for test simplicity
|
||||
|
||||
if not so_contact_id or not so_person_id:
|
||||
print("Error: IDs are required!")
|
||||
return
|
||||
|
||||
print(f"\nSeeding for Company '{company_name}' (ID: {so_contact_id}) and Person (ID: {so_person_id})...")
|
||||
|
||||
# 2. Check/Create Industry
|
||||
industry = session.query(Industry).filter_by(name=industry_name).first()
|
||||
if not industry:
|
||||
industry = Industry(name=industry_name, description="Test Industry")
|
||||
session.add(industry)
|
||||
session.commit()
|
||||
print(f"✅ Created Industry '{industry_name}'")
|
||||
else:
|
||||
print(f"ℹ️ Industry '{industry_name}' exists")
|
||||
|
||||
# 3. Check/Create Job Role
|
||||
role_map = session.query(JobRoleMapping).filter_by(role=person_role).first()
|
||||
if not role_map:
|
||||
role_map = JobRoleMapping(pattern=person_role, role=person_role) # Simple mapping
|
||||
session.add(role_map)
|
||||
session.commit()
|
||||
print(f"✅ Created Role Mapping '{person_role}'")
|
||||
else:
|
||||
print(f"ℹ️ Role Mapping '{person_role}' exists")
|
||||
|
||||
# 4. Check/Create Company
|
||||
company = session.query(Company).filter_by(crm_id=str(so_contact_id)).first()
|
||||
if not company:
|
||||
company = Company(
|
||||
name=company_name,
|
||||
crm_id=str(so_contact_id),
|
||||
industry_ai=industry_name, # Link to our test industry
|
||||
status="ENRICHED"
|
||||
)
|
||||
session.add(company)
|
||||
session.commit()
|
||||
print(f"✅ Created Company '{company_name}' with CRM-ID {so_contact_id}")
|
||||
else:
|
||||
company.industry_ai = industry_name # Ensure correct industry for test
|
||||
session.commit()
|
||||
print(f"ℹ️ Company '{company_name}' exists (Updated Industry)")
|
||||
|
||||
# 5. Check/Create Person
|
||||
person = session.query(Contact).filter_by(so_person_id=int(so_person_id)).first()
|
||||
if not person:
|
||||
person = Contact(
|
||||
company_id=company.id,
|
||||
first_name="Max",
|
||||
last_name="Mustermann",
|
||||
so_person_id=int(so_person_id),
|
||||
so_contact_id=int(so_contact_id),
|
||||
role=person_role
|
||||
)
|
||||
session.add(person)
|
||||
session.commit()
|
||||
print(f"✅ Created Person with SO-ID {so_person_id}")
|
||||
else:
|
||||
person.role = person_role # Ensure role match
|
||||
session.commit()
|
||||
print(f"ℹ️ Person with SO-ID {so_person_id} exists (Updated Role)")
|
||||
|
||||
# 6. Check/Create Matrix Entry
|
||||
matrix = session.query(MarketingMatrix).filter_by(industry_id=industry.id, role_id=role_map.id).first()
|
||||
if not matrix:
|
||||
matrix = MarketingMatrix(
|
||||
industry_id=industry.id,
|
||||
role_id=role_map.id,
|
||||
subject="Test Betreff: Optimierung für {{company_name}}",
|
||||
intro="Hallo, dies ist ein generierter Test-Text aus dem Company Explorer.",
|
||||
social_proof="Wir arbeiten bereits erfolgreich mit anderen Logistikern zusammen."
|
||||
)
|
||||
session.add(matrix)
|
||||
session.commit()
|
||||
print(f"✅ Created Matrix Entry for {industry_name} x {person_role}")
|
||||
else:
|
||||
print(f"ℹ️ Matrix Entry exists")
|
||||
|
||||
print("\n🎉 Seeding Complete! The Company Explorer is ready.")
|
||||
print(f"You can now trigger the Webhook for Contact {so_contact_id} / Person {so_person_id}.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
seed()
|
||||
64
connector-superoffice/tools/so_final_correction.py
Normal file
64
connector-superoffice/tools/so_final_correction.py
Normal file
@@ -0,0 +1,64 @@
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Path gymnastics
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "connector-superoffice"))
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
from company_explorer_connector import get_company_details
|
||||
|
||||
# Load ENV
|
||||
load_dotenv(dotenv_path="/home/node/clawd/.env", override=True)
|
||||
|
||||
def final_correction(ce_id):
|
||||
client = SuperOfficeClient()
|
||||
print(f"--- Final Correction: Applying GROUND TRUTH for CE {ce_id} ---")
|
||||
|
||||
# 1. Force fetch from CE to ensure we have REAL data
|
||||
ce_data = get_company_details(ce_id)
|
||||
if not ce_data or "error" in ce_data:
|
||||
# Fallback to manual ground truth from your message if API still flutters
|
||||
ce_address = "Schatzbogen 39"
|
||||
ce_city = "München"
|
||||
ce_zip = "81829"
|
||||
ce_name = "Robo-Planet GmbH"
|
||||
else:
|
||||
ce_address = ce_data.get("address", "Schatzbogen 39")
|
||||
ce_city = ce_data.get("city", "München")
|
||||
ce_zip = ce_data.get("zip", "81829")
|
||||
ce_name = ce_data.get("name")
|
||||
|
||||
# 2. Map correctly
|
||||
payload = {
|
||||
"contactId": 2,
|
||||
"Name": "RoboPlanet GmbH-SOD",
|
||||
"Number2": "123",
|
||||
"OrgNr": "DE343867623", # Real Robo-Planet VAT
|
||||
"Address": {
|
||||
"Postal": {
|
||||
"Address1": ce_address,
|
||||
"City": ce_city,
|
||||
"Zipcode": ce_zip
|
||||
},
|
||||
"Street": {
|
||||
"Address1": ce_address,
|
||||
"City": ce_city,
|
||||
"Zipcode": ce_zip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
url = f"{client.base_url}/Contact/2"
|
||||
resp = requests.put(url, headers=client.headers, json=payload)
|
||||
|
||||
if resp.status_code == 200:
|
||||
print(f"🚀 SUCCESS! Applied Ground Truth: {ce_address}, {ce_city}")
|
||||
else:
|
||||
print(f"❌ Error: {resp.text}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
final_correction(53)
|
||||
56
connector-superoffice/tools/so_force_write.py
Normal file
56
connector-superoffice/tools/so_force_write.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Path gymnastics
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "connector-superoffice"))
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
# Load ENV
|
||||
load_dotenv(dotenv_path="/home/node/clawd/.env", override=True)
|
||||
|
||||
def force_write(so_id):
|
||||
client = SuperOfficeClient()
|
||||
print(f"--- Force Write-Back: Contact {so_id} ---")
|
||||
|
||||
# Using the mandatory fields you identified and the structured address
|
||||
payload = {
|
||||
"contactId": int(so_id),
|
||||
"Name": "RoboPlanet GmbH-SOD",
|
||||
"Number2": "123", # Mandatory field fix
|
||||
"OrgNr": "DE348572190",
|
||||
"Department": "Force Write 13:35",
|
||||
"Address": {
|
||||
"Postal": {
|
||||
"Address1": "Humboldtstr. 1",
|
||||
"City": "Dornstadt",
|
||||
"Zipcode": "89160"
|
||||
},
|
||||
"Street": {
|
||||
"Address1": "Humboldtstr. 1",
|
||||
"City": "Dornstadt",
|
||||
"Zipcode": "89160"
|
||||
}
|
||||
},
|
||||
"UserDefinedFields": {
|
||||
"SuperOffice:5": "[I:23]" # Vertical: Logistics
|
||||
}
|
||||
}
|
||||
|
||||
url = f"{client.base_url}/Contact/{so_id}"
|
||||
print(f"Sending Force Payload to {url}...")
|
||||
|
||||
resp = requests.put(url, headers=client.headers, json=payload)
|
||||
|
||||
print(f"Status Code: {resp.status_code}")
|
||||
if resp.status_code == 200:
|
||||
print("🚀 SUCCESS! Check Address, VAT and Vertical now.")
|
||||
else:
|
||||
print(f"❌ Error: {resp.text}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
force_write(2)
|
||||
62
connector-superoffice/tools/so_full_enrichment.py
Normal file
62
connector-superoffice/tools/so_full_enrichment.py
Normal file
@@ -0,0 +1,62 @@
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Path gymnastics
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "connector-superoffice"))
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
from company_explorer_connector import get_company_details
|
||||
|
||||
# Load ENV
|
||||
load_dotenv(dotenv_path="/home/node/clawd/.env", override=True)
|
||||
|
||||
def full_enrichment_writeback(ce_id):
|
||||
client = SuperOfficeClient()
|
||||
print(f"--- Full Enrichment Write-Back: CE {ce_id} -> SuperOffice ---")
|
||||
|
||||
# 1. Get data from CE
|
||||
ce_data = get_company_details(ce_id)
|
||||
if not ce_data or "error" in ce_data:
|
||||
print("❌ Could not fetch CE data.")
|
||||
return
|
||||
|
||||
so_id = ce_data.get("crm_id")
|
||||
if not so_id:
|
||||
print("❌ No SO ID found in CE.")
|
||||
return
|
||||
|
||||
# 2. Build Surgical Payload (Postal Address, VAT, Vertical)
|
||||
# We use the specific sub-object structure SO expects
|
||||
payload = {
|
||||
"contactId": int(so_id),
|
||||
"OrgNr": "DE348572190", # Test VAT
|
||||
"Department": "Fully Enriched 13:25",
|
||||
"Address": {
|
||||
"Postal": {
|
||||
"Address1": ce_data.get("address", "Humboldtstr. 1"),
|
||||
"City": ce_data.get("city", "Dornstadt"),
|
||||
"Zipcode": ce_data.get("zip", "89160")
|
||||
}
|
||||
},
|
||||
"UserDefinedFields": {
|
||||
"SuperOffice:5": "[I:23]" # Vertical: Logistics - Warehouse
|
||||
}
|
||||
}
|
||||
|
||||
url = f"{client.base_url}/Contact/{so_id}"
|
||||
print(f"Sending Full Payload to {url}...")
|
||||
|
||||
resp = requests.put(url, headers=client.headers, json=payload)
|
||||
|
||||
print(f"Status Code: {resp.status_code}")
|
||||
if resp.status_code == 200:
|
||||
print("🚀 SUCCESS! Full enrichment (Address, VAT, Vertical) should be visible.")
|
||||
else:
|
||||
print(f"❌ Error: {resp.text}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
full_enrichment_writeback(53)
|
||||
66
connector-superoffice/tools/so_one_shot_fix.py
Normal file
66
connector-superoffice/tools/so_one_shot_fix.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv(dotenv_path="/home/node/clawd/.env", override=True)
|
||||
|
||||
def fix_all_now_v2():
|
||||
# 1. Refresh Token
|
||||
token_url = "https://sod.superoffice.com/login/common/oauth/tokens"
|
||||
token_data = {
|
||||
"grant_type": "refresh_token",
|
||||
"client_id": os.getenv("SO_CLIENT_ID"),
|
||||
"client_secret": os.getenv("SO_CLIENT_SECRET"),
|
||||
"refresh_token": os.getenv("SO_REFRESH_TOKEN"),
|
||||
"redirect_uri": "http://localhost"
|
||||
}
|
||||
t_resp = requests.post(token_url, data=token_data)
|
||||
access_token = t_resp.json().get("access_token")
|
||||
|
||||
if not access_token:
|
||||
print("❌ Token Refresh failed.")
|
||||
return
|
||||
|
||||
# 2. Dual-Url Payload (Root + Array)
|
||||
payload = {
|
||||
"contactId": 2,
|
||||
"Name": "RoboPlanet GmbH-SOD",
|
||||
"Number2": "123",
|
||||
"UrlAddress": "http://robo-planet.de",
|
||||
"Urls": [
|
||||
{
|
||||
"Value": "http://robo-planet.de",
|
||||
"Description": "Website"
|
||||
}
|
||||
],
|
||||
"OrgNr": "DE400464410",
|
||||
"Department": "Website Final Fix 13:42",
|
||||
"Address": {
|
||||
"Postal": {
|
||||
"Address1": "Schatzbogen 39",
|
||||
"City": "München",
|
||||
"Zipcode": "81829"
|
||||
}
|
||||
},
|
||||
"UserDefinedFields": {
|
||||
"SuperOffice:5": "[I:23]"
|
||||
}
|
||||
}
|
||||
|
||||
# 3. Update Call
|
||||
url = "https://app-sod.superoffice.com/Cust55774/api/v1/Contact/2"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {access_token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
resp = requests.put(url, headers=headers, json=payload)
|
||||
|
||||
if resp.status_code == 200:
|
||||
print("🚀 SUCCESS! Website should now be visible via the Urls list.")
|
||||
else:
|
||||
print(f"❌ Error: {resp.text}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
fix_all_now_v2()
|
||||
57
connector-superoffice/tools/so_perfect_sync.py
Normal file
57
connector-superoffice/tools/so_perfect_sync.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import os
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load ENV
|
||||
load_dotenv(dotenv_path="/home/node/clawd/.env", override=True)
|
||||
|
||||
def perfect_sync():
|
||||
# Credentials
|
||||
base_url = "https://app-sod.superoffice.com/Cust55774/api/v1"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {os.getenv('SO_ACCESS_TOKEN')}", # Will be handled by client if needed, but here direct for speed
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
|
||||
# We use the SuperOfficeClient to get a fresh token first
|
||||
from repos.brancheneinstufung2.connector_superoffice.superoffice_client import SuperOfficeClient
|
||||
client = SuperOfficeClient()
|
||||
headers["Authorization"] = f"Bearer {client.access_token}"
|
||||
|
||||
print("--- Perfect Sync: Finalizing Robo-Planet (ID 2) ---")
|
||||
|
||||
payload = {
|
||||
"contactId": 2,
|
||||
"Name": "RoboPlanet GmbH-SOD",
|
||||
"Number2": "123",
|
||||
"UrlAddress": "http://robo-planet.de",
|
||||
"OrgNr": "DE400464410",
|
||||
"Department": "Perfectly Synchronized",
|
||||
"Address": {
|
||||
"Postal": {
|
||||
"Address1": "Schatzbogen 39",
|
||||
"City": "München",
|
||||
"Zipcode": "81829"
|
||||
},
|
||||
"Street": {
|
||||
"Address1": "Schatzbogen 39",
|
||||
"City": "München",
|
||||
"Zipcode": "81829"
|
||||
}
|
||||
},
|
||||
"UserDefinedFields": {
|
||||
"SuperOffice:5": "[I:23]" # Logistics
|
||||
}
|
||||
}
|
||||
|
||||
url = f"{base_url}/Contact/2"
|
||||
resp = requests.put(url, headers=headers, json=payload)
|
||||
|
||||
if resp.status_code == 200:
|
||||
print("🚀 BOOM. Website, VAT, Address and Vertical are now 100% correct.")
|
||||
else:
|
||||
print(f"❌ Error: {resp.text}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
perfect_sync()
|
||||
38
connector-superoffice/tools/so_surgical_update.py
Normal file
38
connector-superoffice/tools/so_surgical_update.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Path gymnastics
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "connector-superoffice"))
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
# Load ENV
|
||||
load_dotenv(dotenv_path="/home/node/clawd/.env", override=True)
|
||||
|
||||
def surgical_update(contact_id):
|
||||
client = SuperOfficeClient()
|
||||
print(f"--- Surgical Update: Contact {contact_id} ---")
|
||||
|
||||
# We use a MINIMAL payload. SuperOffice REST often prefers this for Stammfelder.
|
||||
# Note: Using 'contactId' as it appeared in your discovery log.
|
||||
payload = {
|
||||
"contactId": int(contact_id),
|
||||
"Department": "Surgical Update 13:20",
|
||||
"UrlAddress": "http://robo-planet.de"
|
||||
}
|
||||
|
||||
url = f"{client.base_url}/Contact/{contact_id}"
|
||||
print(f"Sending PUT to {url} with payload: {payload}")
|
||||
|
||||
resp = requests.put(url, headers=client.headers, json=payload)
|
||||
|
||||
print(f"Status Code: {resp.status_code}")
|
||||
print("Full Response Body:")
|
||||
print(json.dumps(resp.json() if resp.content else {}, indent=2))
|
||||
|
||||
if __name__ == "__main__":
|
||||
surgical_update(2)
|
||||
44
connector-superoffice/tools/so_surgical_update_v2.py
Normal file
44
connector-superoffice/tools/so_surgical_update_v2.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Path gymnastics
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "connector-superoffice"))
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
# Load ENV
|
||||
load_dotenv(dotenv_path="/home/node/clawd/.env", override=True)
|
||||
|
||||
def surgical_update_v2(contact_id):
|
||||
client = SuperOfficeClient()
|
||||
print(f"--- Surgical Update V2: Contact {contact_id} ---")
|
||||
|
||||
# We now use the proper 'Urls' list format
|
||||
payload = {
|
||||
"contactId": int(contact_id),
|
||||
"Department": "Final Round-Trip 13:20",
|
||||
"Urls": [
|
||||
{
|
||||
"Value": "http://robo-planet.de",
|
||||
"Description": "Website"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
url = f"{client.base_url}/Contact/{contact_id}"
|
||||
print(f"Sending PUT to {url} with proper URL list...")
|
||||
|
||||
resp = requests.put(url, headers=client.headers, json=payload)
|
||||
|
||||
print(f"Status Code: {resp.status_code}")
|
||||
if resp.status_code == 200:
|
||||
print("✅ SUCCESS! Website should be visible now.")
|
||||
else:
|
||||
print(f"❌ Error: {resp.text}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
surgical_update_v2(2)
|
||||
44
connector-superoffice/tools/so_write_debug.py
Normal file
44
connector-superoffice/tools/so_write_debug.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Path gymnastics
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "connector-superoffice"))
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
# Load ENV
|
||||
load_dotenv(dotenv_path="/home/node/clawd/.env", override=True)
|
||||
|
||||
def debug_update(contact_id):
|
||||
client = SuperOfficeClient()
|
||||
print(f"--- Hard-Debug: Update Contact {contact_id} ---")
|
||||
|
||||
# 1. Fetch full existing object
|
||||
contact = client._get(f"Contact/{contact_id}")
|
||||
if not contact:
|
||||
print("❌ Could not fetch contact.")
|
||||
return
|
||||
|
||||
print(f"Current Name: {contact.get('Name')}")
|
||||
print(f"Current Dept: {contact.get('Department')}")
|
||||
|
||||
# 2. Modify only one simple field
|
||||
contact["Department"] = "AI-Test 13:10"
|
||||
|
||||
# 3. PUT it back
|
||||
print("Sending full object back with modified Department...")
|
||||
result = client._put(f"Contact/{contact_id}", contact)
|
||||
|
||||
if result:
|
||||
print("✅ API accepted the update.")
|
||||
# Verify immediately
|
||||
updated = client._get(f"Contact/{contact_id}")
|
||||
print(f"New Department in SO: {updated.get('Department')}")
|
||||
else:
|
||||
print("❌ Update failed.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
debug_update(2)
|
||||
66
connector-superoffice/tools/sync_ce_to_so_test.py
Normal file
66
connector-superoffice/tools/sync_ce_to_so_test.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Path gymnastics
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "connector-superoffice"))
|
||||
|
||||
from company_explorer_connector import get_company_details
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
# Load ENV
|
||||
load_dotenv(dotenv_path="/home/node/clawd/.env", override=True)
|
||||
|
||||
def round_trip_test(ce_id):
|
||||
print(f"--- Starting Round-Trip POC: CE-ID {ce_id} -> SuperOffice ---")
|
||||
|
||||
# 1. Get enriched data from Company Explorer
|
||||
ce_data = get_company_details(ce_id)
|
||||
if not ce_data or "error" in ce_data:
|
||||
print(f"❌ ERROR: Could not fetch data from Company Explorer for ID {ce_id}")
|
||||
return
|
||||
|
||||
print(f"✅ Success: Received data from CE for '{ce_data.get('name')}'")
|
||||
|
||||
# 2. Extract CRM ID
|
||||
so_id = ce_data.get("crm_id")
|
||||
if not so_id:
|
||||
print("❌ ERROR: No crm_id found in Company Explorer for this record. Cannot sync back.")
|
||||
return
|
||||
|
||||
print(f"Targeting SuperOffice Contact ID: {so_id}")
|
||||
|
||||
# 3. Prepare SuperOffice Update Payload
|
||||
# Based on your request: Address, Website, Email, Phone
|
||||
# Note: We need to match the SO schema (Street Address vs Postal Address)
|
||||
so_payload = {
|
||||
"Name": ce_data.get("name"),
|
||||
"UrlAddress": ce_data.get("website"),
|
||||
"Address": {
|
||||
"Street": {
|
||||
"Address1": ce_data.get("address", ""), # Simplified mapping for POC
|
||||
"City": ce_data.get("city", ""),
|
||||
"Zipcode": ce_data.get("zip", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 4. Perform Update via SuperOfficeClient
|
||||
client = SuperOfficeClient()
|
||||
print(f"Updating SuperOffice Contact {so_id}...")
|
||||
|
||||
# Using the generic PUT method from our client
|
||||
endpoint = f"Contact/{so_id}"
|
||||
result = client._put(endpoint, so_payload)
|
||||
|
||||
if result:
|
||||
print(f"🚀 SUCCESS! Round-trip complete. SuperOffice Contact {so_id} updated.")
|
||||
print(f"Updated Data: {ce_data.get('website')} | {ce_data.get('city')}")
|
||||
else:
|
||||
print("❌ ERROR: Failed to update SuperOffice.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test with the ID you manually enriched
|
||||
round_trip_test(53)
|
||||
41
connector-superoffice/tools/sync_test_roboplanet.py
Normal file
41
connector-superoffice/tools/sync_test_roboplanet.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Path gymnastics to ensure imports work from the current directory
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "connector-superoffice"))
|
||||
|
||||
from company_explorer_connector import handle_company_workflow
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
# Load ENV from correct path
|
||||
load_dotenv(dotenv_path="/home/node/clawd/.env", override=True)
|
||||
|
||||
def sync_roboplanet():
|
||||
print("--- Starting Sync Test: SuperOffice -> Company Explorer ---")
|
||||
|
||||
# 1. Fetch Contact from SuperOffice
|
||||
client = SuperOfficeClient()
|
||||
contact_id = 2
|
||||
print(f"Fetching Contact ID {contact_id} from SuperOffice...")
|
||||
contact_so = client._get(f"Contact/{contact_id}")
|
||||
|
||||
if not contact_so:
|
||||
print("❌ ERROR: Could not find Contact ID 2 in SuperOffice.")
|
||||
return
|
||||
|
||||
company_name = contact_so.get("Name")
|
||||
print(f"✅ Success: Found '{company_name}' in SuperOffice.")
|
||||
|
||||
# 2. Push to Company Explorer
|
||||
print(f"\nPushing '{company_name}' to Company Explorer via Connector...")
|
||||
# Using the workflow to check existence and create if needed
|
||||
result = handle_company_workflow(company_name)
|
||||
|
||||
print("\n--- WORKFLOW RESULT ---")
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||
|
||||
if __name__ == "__main__":
|
||||
sync_roboplanet()
|
||||
41
connector-superoffice/tools/test_selection_membership.py
Normal file
41
connector-superoffice/tools/test_selection_membership.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# Absolute path setup
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
connector_dir = os.path.abspath(os.path.join(current_dir, '..'))
|
||||
sys.path.insert(0, connector_dir)
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
def test_membership(contact_id: int):
|
||||
selection_id = 10960
|
||||
print(f"🔎 Testing if Contact {contact_id} is member of Selection {selection_id}...")
|
||||
client = SuperOfficeClient()
|
||||
|
||||
# Efficient Membership Check
|
||||
# GET Selection/{id}/MemberStatus/Contact/{contactId}
|
||||
endpoint = f"Selection/{selection_id}/MemberStatus/Contact/{contact_id}"
|
||||
|
||||
print(f"📡 Querying: {endpoint}")
|
||||
try:
|
||||
resp = client._get(endpoint)
|
||||
print(f"✅ Response: {json.dumps(resp, indent=2)}")
|
||||
|
||||
# Result format is usually a string: "Member", "NotMember", "Excluded"
|
||||
if resp == "Member":
|
||||
print("🎯 YES: Contact is a member.")
|
||||
else:
|
||||
print("⏭️ NO: Contact is NOT a member.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Membership check failed: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test with Tanja Ullmann (171188) which we identified as Roboplanet
|
||||
test_membership(171188)
|
||||
|
||||
# Test with Wackler parent (ID 3)
|
||||
print("\n--- Control Test ---")
|
||||
test_membership(3)
|
||||
46
connector-superoffice/tools/verify_enrichment.py
Normal file
46
connector-superoffice/tools/verify_enrichment.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import sys
|
||||
import os
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Explicitly load .env
|
||||
dotenv_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '.env'))
|
||||
load_dotenv(dotenv_path=dotenv_path, override=True)
|
||||
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
def final_proof(contact_id: int):
|
||||
print(f"🚀 Final Data Check for ContactId: {contact_id}")
|
||||
try:
|
||||
client = SuperOfficeClient()
|
||||
# Get RAW text to be 100% safe
|
||||
url = f"{client.base_url}/Contact/{contact_id}?$select=Name,UserDefinedFields"
|
||||
resp = requests.get(url, headers=client.headers)
|
||||
raw_text = resp.text
|
||||
|
||||
print("\n--- 🔍 EVIDENCE CHECK ---")
|
||||
print(f"Company Name found: {'Bremer Abenteuerland' in raw_text}")
|
||||
|
||||
# Check for the Vertical ID '1628' (Leisure - Indoor Active)
|
||||
if '"SuperOffice:83":"[I:1628]"' in raw_text:
|
||||
print("✅ SUCCESS: Vertical 'Leisure - Indoor Active' (1628) is correctly set in SuperOffice!")
|
||||
elif "1628" in raw_text:
|
||||
print("⚠️ FOUND '1628' in response, but not in the expected field format.")
|
||||
else:
|
||||
print("❌ FAILURE: Vertical ID '1628' not found in SuperOffice response.")
|
||||
|
||||
# Check for Summary (truncated)
|
||||
if "Abenteuerland" in raw_text and "SuperOffice:84" in raw_text:
|
||||
print("✅ SUCCESS: AI Summary field (SuperOffice:84) seems to contain data.")
|
||||
|
||||
print("\n--- Summary of RAW Data (UDF part) ---")
|
||||
# Just show a bit of the UDFs
|
||||
start_idx = raw_text.find("UserDefinedFields")
|
||||
print(raw_text[start_idx:start_idx+500] + "...")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
final_proof(171185)
|
||||
80
connector-superoffice/tools/verify_latest_roboplanet.py
Normal file
80
connector-superoffice/tools/verify_latest_roboplanet.py
Normal file
@@ -0,0 +1,80 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# Absolute path setup
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
connector_dir = os.path.abspath(os.path.join(current_dir, '..'))
|
||||
sys.path.insert(0, connector_dir)
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
from config import settings
|
||||
|
||||
def find_latest_match():
|
||||
print("🔎 Searching for the youngest account assigned to a Roboplanet user...")
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Auth failed.")
|
||||
return
|
||||
|
||||
whitelist = settings.ROBOPLANET_WHITELIST
|
||||
print(f"📋 Whitelist contains {len(whitelist)} entries (IDs + Names).")
|
||||
|
||||
# 1. Fetch more contacts to find a match
|
||||
limit = 1000
|
||||
endpoint = f"Contact?$orderby=contactId desc&$top={limit}&$select=contactId,name,associateId"
|
||||
|
||||
print(f"📡 Fetching latest {limit} contacts (this may take a few seconds)...")
|
||||
try:
|
||||
contacts = client.search(endpoint)
|
||||
if not contacts:
|
||||
print("❌ No contacts returned from API.")
|
||||
return
|
||||
|
||||
print(f"✅ Received {len(contacts)} contacts. Checking against whitelist...")
|
||||
|
||||
found = False
|
||||
for i, c in enumerate(contacts):
|
||||
if i > 0 and i % 100 == 0:
|
||||
print(f" ... checked {i} records ...")
|
||||
|
||||
cid = c.get('contactId') or c.get('ContactId')
|
||||
cname = c.get('name') or c.get('Name')
|
||||
|
||||
# Extract associate identifier (might be ID or Name)
|
||||
raw_aid = c.get('associateId') or c.get('AssociateId')
|
||||
|
||||
is_match = False
|
||||
if raw_aid:
|
||||
# 1. Try as String (Name)
|
||||
val_str = str(raw_aid).upper().strip()
|
||||
if val_str in whitelist:
|
||||
is_match = True
|
||||
else:
|
||||
# 2. Try as Int (ID)
|
||||
try:
|
||||
if int(raw_aid) in whitelist:
|
||||
is_match = True
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
if is_match:
|
||||
print("\n🎯 FOUND YOUNGEST ROBOPLANET ACCOUNT:")
|
||||
print(f" - Company Name: {cname}")
|
||||
print(f" - Contact ID: {cid}")
|
||||
print(f" - Responsible Identifier: {raw_aid}")
|
||||
print(f" - Link: https://online3.superoffice.com/Cust26720/default.aspx?contact?contact_id={cid}")
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
print(f"\n⚠️ No match found in the last {limit} contacts.")
|
||||
print(" This confirms that recent activity is from non-whitelist users.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
if __name__ == "__main__":
|
||||
find_latest_match()
|
||||
@@ -0,0 +1,67 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# Absolute path setup
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
connector_dir = os.path.abspath(os.path.join(current_dir, '..'))
|
||||
sys.path.insert(0, connector_dir)
|
||||
|
||||
from superoffice_client import SuperOfficeClient
|
||||
from config import settings
|
||||
|
||||
def verify():
|
||||
selection_id = 10960
|
||||
print(f"🔎 Verifying members of Selection {selection_id}...")
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Auth failed.")
|
||||
return
|
||||
|
||||
# Use the Selection/ID/ContactMembers endpoint which is part of the REST API
|
||||
# We ask for a few members and their Associate info
|
||||
endpoint = f"Selection/{selection_id}/ContactMembers?$top=50&$select=ContactId,Name,AssociateId"
|
||||
|
||||
print(f"📡 Querying: {endpoint}")
|
||||
try:
|
||||
resp = client._get(endpoint)
|
||||
# OData returns 'value'
|
||||
members = resp.get('value', [])
|
||||
|
||||
if not members:
|
||||
print("⚠️ No members found via REST. Trying alternative Archive call...")
|
||||
# If REST fails, we might have to use a different approach
|
||||
return
|
||||
|
||||
print(f"✅ Found {len(members)} members. Inspecting owners...")
|
||||
|
||||
whitelist = settings.ROBOPLANET_WHITELIST
|
||||
owners_found = {}
|
||||
|
||||
for m in members:
|
||||
cid = m.get('ContactId')
|
||||
cname = m.get('Name')
|
||||
# The AssociateId might be named differently in the response
|
||||
aid = m.get('AssociateId')
|
||||
|
||||
if aid:
|
||||
is_robo = aid in whitelist or str(aid).upper() in whitelist
|
||||
status = "✅ ROBO" if is_robo else "❌ STRANGER"
|
||||
owners_found[aid] = (status, aid)
|
||||
# print(f" - Contact {cid} ({cname}): Owner {aid} [{status}]")
|
||||
|
||||
print("\n📊 Summary of Owners in Selection:")
|
||||
for aid, (status, val) in owners_found.items():
|
||||
print(f" {status}: Associate {aid}")
|
||||
|
||||
if any("STRANGER" in s for s, v in owners_found.values()):
|
||||
print("\n⚠️ ALERT: Found owners in the selection who are NOT in our whitelist.")
|
||||
print("This explains the delta. Please check if these IDs should be added.")
|
||||
else:
|
||||
print("\n✅ All sampled members belong to whitelist users.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
verify()
|
||||
42
connector-superoffice/tools/who_am_i.py
Normal file
42
connector-superoffice/tools/who_am_i.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Explicitly load .env from the project root
|
||||
dotenv_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '.env'))
|
||||
load_dotenv(dotenv_path=dotenv_path, override=True)
|
||||
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
from superoffice_client import SuperOfficeClient
|
||||
|
||||
def get_current_user():
|
||||
print(f"🚀 Fetching current user info via Associate/Me...")
|
||||
try:
|
||||
client = SuperOfficeClient()
|
||||
if not client.access_token:
|
||||
print("❌ Authentication failed.")
|
||||
return
|
||||
|
||||
# Try the most reliable endpoint for current user context
|
||||
user = client._get("Associate/Me")
|
||||
|
||||
if user:
|
||||
print("\n--- 👤 Current User Info ---")
|
||||
print(f"Associate ID: {user.get('AssociateId')}")
|
||||
print(f"Name: {user.get('FullName')}")
|
||||
print(f"UserName: {user.get('UserName')}")
|
||||
print("----------------------------")
|
||||
return user.get('AssociateId')
|
||||
else:
|
||||
# Fallback: List all associates and try to match by name or username
|
||||
print("⚠️ Associate/Me failed. Trying alternative...")
|
||||
# This might be too much data, but let's see
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return None
|
||||
|
||||
if __name__ == "__main__":
|
||||
get_current_user()
|
||||
Reference in New Issue
Block a user