diff --git a/connector-superoffice/generate_auth_url.py b/connector-superoffice/generate_auth_url.py index 1349f56f..97d3c18f 100644 --- a/connector-superoffice/generate_auth_url.py +++ b/connector-superoffice/generate_auth_url.py @@ -3,7 +3,7 @@ from dotenv import load_dotenv import urllib.parse def generate_url(): - load_dotenv(dotenv_path="../.env") + load_dotenv(dotenv_path="/home/node/clawd/.env") client_id = os.getenv("SO_CLIENT_ID") or os.getenv("SO_SOD") redirect_uri = "https://devnet-tools.superoffice.com/openid/callback" # Das muss im Portal so registriert sein diff --git a/connector-superoffice/test_client.py b/connector-superoffice/test_client.py new file mode 100644 index 00000000..64ca9e73 --- /dev/null +++ b/connector-superoffice/test_client.py @@ -0,0 +1,28 @@ +# test_client.py +import os +from dotenv import load_dotenv +from superoffice_client import SuperOfficeClient + +print("--- Testing Core SuperOfficeClient ---") + +# Load environment variables from the root .env file +load_dotenv(dotenv_path="/home/node/clawd/.env") + +try: + # Initialize the client + client = SuperOfficeClient() + + # Perform a simple read operation + person_id = 1 + print(f"Fetching person with ID: {person_id}...") + person_data = client.get_person(person_id) + + if person_data: + print(f"✅ SUCCESS! Found Person: {person_data.get('firstname')} {person_data.get('lastname')}") + else: + print(f"❌ ERROR: Could not fetch person {person_id}, but connection was successful.") + +except Exception as e: + print(f"❌ FATAL ERROR during client initialization or request: {e}") + +print("--- Test complete ---") diff --git a/inspect_db_schema.py b/inspect_db_schema.py new file mode 100644 index 00000000..46aa3a63 --- /dev/null +++ b/inspect_db_schema.py @@ -0,0 +1,24 @@ +import sqlite3 +import os + +db_path = "/home/node/clawd/repos/brancheneinstufung2/company-explorer/backend/companies_v3_fixed_2.db" + +if not os.path.exists(db_path): + # Fallback to check other potential locations + db_path = "/home/node/clawd/repos/brancheneinstufung2/companies_v3_fixed_2.db" + +print(f"--- Inspecting Database: {db_path} ---") + +try: + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + cursor.execute("PRAGMA table_info(companies)") + columns = cursor.fetchall() + + print("Columns in table 'companies':") + for col in columns: + print(f"- {col[1]} ({col[2]})") + + conn.close() +except Exception as e: + print(f"Error: {e}") diff --git a/so_final_correction.py b/so_final_correction.py new file mode 100644 index 00000000..006357f4 --- /dev/null +++ b/so_final_correction.py @@ -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) diff --git a/so_force_write.py b/so_force_write.py new file mode 100644 index 00000000..5a68352a --- /dev/null +++ b/so_force_write.py @@ -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) diff --git a/so_full_enrichment.py b/so_full_enrichment.py new file mode 100644 index 00000000..79f23b02 --- /dev/null +++ b/so_full_enrichment.py @@ -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) diff --git a/so_perfect_sync.py b/so_perfect_sync.py new file mode 100644 index 00000000..69fe7f4e --- /dev/null +++ b/so_perfect_sync.py @@ -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() diff --git a/so_surgical_update.py b/so_surgical_update.py new file mode 100644 index 00000000..9c5ad52f --- /dev/null +++ b/so_surgical_update.py @@ -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) diff --git a/so_write_debug.py b/so_write_debug.py new file mode 100644 index 00000000..4ab135d9 --- /dev/null +++ b/so_write_debug.py @@ -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) diff --git a/sync_ce_to_so_test.py b/sync_ce_to_so_test.py new file mode 100644 index 00000000..4cfc921c --- /dev/null +++ b/sync_ce_to_so_test.py @@ -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) diff --git a/sync_test_roboplanet.py b/sync_test_roboplanet.py new file mode 100644 index 00000000..d9708d5c --- /dev/null +++ b/sync_test_roboplanet.py @@ -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() diff --git a/test_explorer_connection.py b/test_explorer_connection.py new file mode 100644 index 00000000..c9c182f0 --- /dev/null +++ b/test_explorer_connection.py @@ -0,0 +1,31 @@ +import requests +import os +from requests.auth import HTTPBasicAuth + +def test_connection(url, name): + print(f"--- Testing {name}: {url} ---") + try: + # We try the health endpoint + response = requests.get( + f"{url}/health", + auth=HTTPBasicAuth("admin", "gemini"), + timeout=5 + ) + print(f"Status Code: {response.status_code}") + print(f"Response: {response.text}") + return response.status_code == 200 + except Exception as e: + print(f"Error: {e}") + return False + +# Path 1: Hardcoded LAN IP through Proxy +url_lan = "http://192.168.178.6:8090/ce/api" +# Path 2: Internal Docker Networking (direct) +url_docker = "http://company-explorer:8000/api" + +success_lan = test_connection(url_lan, "LAN IP (Proxy)") +print("\n") +success_docker = test_connection(url_docker, "Docker Internal (Direct)") + +if not success_lan and not success_docker: + print("\nFATAL: Company Explorer not reachable from this container.")