feat: Add EspoCRM and Advoware integration for Beteiligte comparison

- Implemented `compare_beteiligte.py` script for comparing Beteiligte structures between EspoCRM and Advoware.
- Created `beteiligte_comparison_result.json` to store comparison results.
- Developed `EspoCRMAPI` service for handling API interactions with EspoCRM.
- Added comprehensive documentation for the EspoCRM API service.
- Included error handling and logging for API operations.
- Enhanced entity management with CRUD operations and search capabilities.
This commit is contained in:
2026-02-07 14:42:58 +00:00
parent 36552903e7
commit e6ab22d5f4
12 changed files with 2143 additions and 1426 deletions

296
bitbylaw/scripts/README.md Normal file
View File

@@ -0,0 +1,296 @@
# Beteiligte Structure Comparison Tool
## Purpose
This helper script fetches entity data from both **EspoCRM** and **Advoware** to compare their data structures. This helps understand:
- What fields exist in each system
- How field names differ
- Potential field mappings for synchronization
- Data type differences
## Usage
```bash
cd /opt/motia-app
# Basic usage: Compare by EspoCRM ID (will auto-search in Advoware)
python bitbylaw/scripts/compare_beteiligte.py <espocrm_entity_id>
# Advanced: Specify both IDs
python bitbylaw/scripts/compare_beteiligte.py <espocrm_entity_id> <advoware_id>
```
## Examples
```bash
# Example 1: Fetch from EspoCRM and search in Advoware by name
python bitbylaw/scripts/compare_beteiligte.py 64a3f2b8c9e1234567890abc
# Example 2: Fetch from both systems by ID
python bitbylaw/scripts/compare_beteiligte.py 64a3f2b8c9e1234567890abc 12345
# Example 3: Using the virtual environment
source python_modules/bin/activate
python bitbylaw/scripts/compare_beteiligte.py 64a3f2b8c9e1234567890abc
```
## Requirements
### Environment Variables
Make sure these are set in `.env` or environment:
```bash
# EspoCRM
ESPOCRM_API_BASE_URL=https://crm.bitbylaw.com/api/v1
ESPOCRM_MARVIN_API_KEY=your_api_key_here
# Advoware
ADVOWARE_API_BASE_URL=https://www2.advo-net.net:90/
ADVOWARE_API_KEY=your_base64_encoded_key
ADVOWARE_USER=your_user
ADVOWARE_PASSWORD=your_password
ADVOWARE_KANZLEI=your_kanzlei
ADVOWARE_DATABASE=your_database
# ... (see config.py for all required vars)
```
### Dependencies
```bash
pip install aiohttp redis python-dotenv requests
```
## Output
The script produces:
### 1. Console Output
```
================================================================================
BETEILIGTE STRUCTURE COMPARISON TOOL
================================================================================
EspoCRM Entity ID: 64a3f2b8c9e1234567890abc
Environment Check:
----------------------------------------
ESPOCRM_API_BASE_URL: https://crm.bitbylaw.com/api/v1
ESPOCRM_API_KEY: ✓ Set
ADVOWARE_API_BASE_URL: https://www2.advo-net.net:90/
ADVOWARE_API_KEY: ✓ Set
================================================================================
ESPOCRM - Fetching Beteiligter
================================================================================
Trying entity type: Beteiligte
✓ Success! Found in Beteiligte
Entity Structure:
--------------------------------------------------------------------------------
{
"id": "64a3f2b8c9e1234567890abc",
"name": "Max Mustermann",
"firstName": "Max",
"lastName": "Mustermann",
"email": "max@example.com",
"phone": "+49123456789",
...
}
================================================================================
ADVOWARE - Fetching Beteiligter
================================================================================
Searching by name: Max Mustermann
Trying endpoint: /contacts
✓ Found 2 results
Search Results:
--------------------------------------------------------------------------------
[
{
"id": 12345,
"full_name": "Max Mustermann",
"email": "max@example.com",
...
}
]
================================================================================
STRUCTURE COMPARISON
================================================================================
EspoCRM Fields (25):
----------------------------------------
id (str)
name (str)
firstName (str)
lastName (str)
email (str)
phone (str)
...
Advoware Fields (30):
----------------------------------------
id (int)
full_name (str)
email (str)
phone_number (str)
...
Common Fields (5):
----------------------------------------
✓ id
✓ email
✗ phone
EspoCRM: +49123456789
Advoware: 0123456789
EspoCRM Only (20):
----------------------------------------
firstName
lastName
...
Advoware Only (25):
----------------------------------------
full_name
phone_number
...
Potential Field Mappings:
----------------------------------------
firstName → first_name
lastName → last_name
email → email
phone → phone_number
...
================================================================================
Comparison saved to: /opt/motia-app/bitbylaw/scripts/beteiligte_comparison_result.json
================================================================================
```
### 2. JSON Output File
Saved to `bitbylaw/scripts/beteiligte_comparison_result.json`:
```json
{
"espocrm_data": {
"id": "64a3f2b8c9e1234567890abc",
"name": "Max Mustermann",
...
},
"advoware_data": {
"id": 12345,
"full_name": "Max Mustermann",
...
},
"comparison": {
"espo_fields": ["id", "name", "firstName", ...],
"advo_fields": ["id", "full_name", "email", ...],
"common": ["id", "email"],
"espo_only": ["firstName", "lastName", ...],
"advo_only": ["full_name", "phone_number", ...],
"suggested_mappings": [
["firstName", "first_name"],
["lastName", "last_name"],
...
]
}
}
```
## How It Works
### 1. EspoCRM Fetch
The script tries multiple entity types to find the data:
- `Beteiligte` (custom VMH entity)
- `Contact` (standard)
- `Account` (standard)
- `Lead` (standard)
### 2. Advoware Fetch
**By ID (if provided):**
- Tries: `/contacts/{id}`, `/parties/{id}`, `/clients/{id}`
**By Name (if EspoCRM data available):**
- Searches: `/contacts?search=...`, `/parties?search=...`, `/clients?search=...`
### 3. Comparison
- Lists all fields from both systems
- Identifies common fields (same name)
- Shows values for common fields
- Suggests mappings based on naming patterns
- Exports full comparison to JSON
## Troubleshooting
### "ESPOCRM_API_KEY not set"
```bash
# Check if .env exists and has the key
cat .env | grep ESPOCRM_MARVIN_API_KEY
# Or set it manually
export ESPOCRM_MARVIN_API_KEY=your_key_here
```
### "Authentication failed - check API key"
1. Verify API key in EspoCRM admin panel
2. Check API User permissions
3. Ensure API User has access to entity type
### "Entity not found"
- Check if entity ID is correct
- Verify entity type exists in EspoCRM
- Check API User permissions for that entity
### "Advoware token error"
- Verify all Advoware credentials in `.env`
- Check HMAC signature generation
- Ensure API key is base64 encoded
- Test token generation separately
## Next Steps
After running this script:
1. **Review JSON output** - Check `beteiligte_comparison_result.json`
2. **Define mappings** - Create mapping table based on suggestions
3. **Implement mapper** - Create transformation functions
4. **Test sync** - Use mappings in sync event step
Example mapping implementation:
```python
def map_espocrm_to_advoware(espo_entity: dict) -> dict:
"""Transform EspoCRM Beteiligter to Advoware format"""
return {
'first_name': espo_entity.get('firstName'),
'last_name': espo_entity.get('lastName'),
'email': espo_entity.get('email'),
'phone_number': espo_entity.get('phone'),
# Add more mappings based on comparison...
}
```
## Related Files
- [services/espocrm.py](../services/espocrm.py) - EspoCRM API client
- [services/advoware.py](../services/advoware.py) - Advoware API client
- [services/ESPOCRM_SERVICE.md](../services/ESPOCRM_SERVICE.md) - EspoCRM docs
- [config.py](../config.py) - Configuration

View File

@@ -0,0 +1,399 @@
{
"espocrm_data": {
"id": "68e4af00172be7924",
"name": "dasdas dasdasdas dasdasdas",
"deleted": false,
"salutationName": null,
"rechtsform": "GmbH",
"firmenname": "Filli llu GmbH",
"firstName": "dasdasdas",
"lastName": "dasdas",
"dateOfBirth": null,
"description": null,
"emailAddress": "meier@meier.de",
"phoneNumber": null,
"createdAt": "2025-10-07 06:11:12",
"modifiedAt": "2026-01-23 21:58:41",
"betnr": 1234,
"advowareLastSync": null,
"syncStatus": "clean",
"handelsregisterNummer": "12244546",
"handelsregisterArt": "HRB",
"disgTyp": "Unbekannt",
"middleName": "dasdasdas",
"emailAddressIsOptedOut": false,
"emailAddressIsInvalid": false,
"phoneNumberIsOptedOut": null,
"phoneNumberIsInvalid": null,
"streamUpdatedAt": null,
"emailAddressData": [
{
"emailAddress": "meier@meier.de",
"lower": "meier@meier.de",
"primary": true,
"optOut": false,
"invalid": false
},
{
"emailAddress": "a@r028tuj08wefj0w8efjw0d.de",
"lower": "a@r028tuj08wefj0w8efjw0d.de",
"primary": false,
"optOut": false,
"invalid": false
}
],
"phoneNumberData": [],
"createdById": "68d65929f18c2afef",
"createdByName": "Admin",
"modifiedById": "68d65929f18c2afef",
"modifiedByName": "Admin",
"assignedUserId": null,
"assignedUserName": null,
"teamsIds": [],
"teamsNames": {},
"adressensIds": [],
"adressensNames": {},
"calls1Ids": [],
"calls1Names": {},
"bankverbindungensIds": [],
"bankverbindungensNames": {},
"isFollowed": false,
"followersIds": [],
"followersNames": {}
},
"advoware_data": {
"betNr": 104860,
"kommunikation": [
{
"rowId": "FBABAAAANJFGABAAGJDOAEAPAAAAAPGFPDAFAAAA",
"id": 88002,
"betNr": 104860,
"kommArt": 0,
"tlf": "0511/12345-60",
"bemerkung": null,
"kommKz": 0,
"online": false
},
{
"rowId": "FBABAAAABBLIABAAGIDOAEAPAAAAAPHBEOAEAAAA",
"id": 114914,
"betNr": 104860,
"kommArt": 0,
"tlf": "kanzlei@ralup.de",
"bemerkung": null,
"kommKz": 0,
"online": true
}
],
"kontaktpersonen": [],
"beteiligungen": [
{
"rowId": "LAADAAAAAHMDABAAGAAEIPBAAAAADGKEMPAFAAAA",
"beteiligtenArt": "Sachverständiger",
"akte": {
"rowId": "",
"nr": 2020001684,
"az": "1684/20",
"rubrum": "Siggel / Siggel",
"referat": "SON",
"wegen": "Bruderzwist II",
"ablage": 1,
"abgelegt": null
}
},
{
"rowId": "LAADAAAAPGKFABAAGAAEIPBAAAAADGJOMBABAAAA",
"beteiligtenArt": "Sachverständiger",
"akte": {
"rowId": "",
"nr": 2020000203,
"az": "203/20",
"rubrum": "Siggel / Siggel",
"referat": "SON",
"wegen": "Bruderzwist",
"ablage": 1,
"abgelegt": null
}
},
{
"rowId": "LAADAAAAPJAGACAAGAAEIPBAAAAADGLDFGADAAAA",
"beteiligtenArt": "Mandant",
"akte": {
"rowId": "",
"nr": 2019001145,
"az": "1145/19",
"rubrum": "Siggel / Siggel LALA",
"referat": "VMH",
"wegen": null,
"ablage": 0,
"abgelegt": null
}
}
],
"adressen": [
{
"rowId": "KOADAAAAALNFAAAAFPAEIPBAAAAADGGPGAAJAAAA",
"id": 0,
"beteiligterId": 104860,
"reihenfolgeIndex": 1,
"strasse": "Musterstraße 12",
"plz": "12345",
"ort": "Musterort",
"land": "D",
"postfach": null,
"postfachPLZ": null,
"anschrift": "Frau\r\nAngela Mustermanns\r\nVorzimmer\r\nMusterstraße 12\r\n12345 Musterort",
"standardAnschrift": false,
"bemerkung": null,
"gueltigVon": null,
"gueltigBis": null
}
],
"bankkverbindungen": [
{
"rowId": "EPABAAAAHBNFAAAAFPNBCGAAAAAAAPDIJDAJAAAA",
"id": 54665,
"bank": null,
"ktoNr": null,
"blz": null,
"iban": null,
"bic": null,
"kontoinhaber": null,
"mandatsreferenz": null,
"mandatVom": null
}
],
"rowId": "EMABAAAAFBNFAAAAFOAEIPBAAAAAAOMNKPAHAAAA",
"id": 104860,
"anschrift": "Frau\r\nAngela Mustermanns\r\nVorzimmer\r\nMusterstraße 12\r\n12345 Musterort",
"strasse": "Musterstraße 12",
"plz": "12345",
"ort": "Musterort",
"email": null,
"emailGesch": "kanzlei@ralup.de",
"mobil": null,
"internet": null,
"telGesch": "0511/12345-60",
"telPrivat": null,
"faxGesch": null,
"faxPrivat": null,
"autotelefon": null,
"sonstige": null,
"ePost": null,
"bea": null,
"art": null,
"vorname": "Angela",
"name": "Mustermanns",
"kurzname": null,
"geburtsname": null,
"familienstand": null,
"titel": null,
"anrede": "Frau",
"bAnrede": "Sehr geehrte Frau Mustermanns,",
"geburtsdatum": null,
"sterbedatum": null,
"zusatz": "Vorzimmer",
"rechtsform": "Frau",
"geaendertAm": null,
"geaendertVon": null,
"angelegtAm": null,
"angelegtVon": null,
"handelsRegisterNummer": null,
"registergericht": null
},
"comparison": {
"espo_fields": [
"emailAddressIsInvalid",
"followersNames",
"id",
"handelsregisterNummer",
"teamsNames",
"assignedUserName",
"modifiedAt",
"modifiedByName",
"betnr",
"middleName",
"disgTyp",
"bankverbindungensNames",
"phoneNumberIsOptedOut",
"adressensIds",
"emailAddressData",
"deleted",
"teamsIds",
"phoneNumber",
"isFollowed",
"advowareLastSync",
"createdById",
"createdAt",
"calls1Ids",
"handelsregisterArt",
"name",
"phoneNumberIsInvalid",
"rechtsform",
"emailAddress",
"emailAddressIsOptedOut",
"firmenname",
"description",
"adressensNames",
"createdByName",
"lastName",
"assignedUserId",
"salutationName",
"bankverbindungensIds",
"phoneNumberData",
"dateOfBirth",
"modifiedById",
"firstName",
"followersIds",
"streamUpdatedAt",
"syncStatus",
"calls1Names"
],
"advo_fields": [
"kontaktpersonen",
"rowId",
"id",
"angelegtVon",
"zusatz",
"bAnrede",
"faxGesch",
"bankkverbindungen",
"geburtsname",
"plz",
"adressen",
"kurzname",
"telPrivat",
"anrede",
"sonstige",
"email",
"titel",
"sterbedatum",
"faxPrivat",
"autotelefon",
"name",
"kommunikation",
"rechtsform",
"art",
"geaendertAm",
"anschrift",
"beteiligungen",
"bea",
"handelsRegisterNummer",
"registergericht",
"internet",
"ort",
"geburtsdatum",
"angelegtAm",
"mobil",
"emailGesch",
"ePost",
"strasse",
"vorname",
"familienstand",
"betNr",
"geaendertVon",
"telGesch"
],
"common": [
"name",
"id",
"rechtsform"
],
"espo_only": [
"emailAddressIsInvalid",
"followersNames",
"handelsregisterNummer",
"teamsNames",
"assignedUserName",
"modifiedAt",
"modifiedByName",
"betnr",
"middleName",
"disgTyp",
"bankverbindungensNames",
"phoneNumberIsOptedOut",
"adressensIds",
"emailAddressData",
"deleted",
"teamsIds",
"phoneNumber",
"isFollowed",
"advowareLastSync",
"createdById",
"createdAt",
"calls1Ids",
"handelsregisterArt",
"phoneNumberIsInvalid",
"emailAddress",
"emailAddressIsOptedOut",
"firmenname",
"description",
"adressensNames",
"createdByName",
"lastName",
"assignedUserId",
"salutationName",
"bankverbindungensIds",
"phoneNumberData",
"dateOfBirth",
"modifiedById",
"firstName",
"followersIds",
"streamUpdatedAt",
"syncStatus",
"calls1Names"
],
"advo_only": [
"kontaktpersonen",
"rowId",
"angelegtVon",
"zusatz",
"bAnrede",
"faxGesch",
"bankkverbindungen",
"geburtsname",
"plz",
"adressen",
"kurzname",
"telPrivat",
"anrede",
"sonstige",
"email",
"titel",
"sterbedatum",
"autotelefon",
"faxPrivat",
"kommunikation",
"art",
"geaendertAm",
"anschrift",
"beteiligungen",
"bea",
"handelsRegisterNummer",
"registergericht",
"internet",
"ort",
"geburtsdatum",
"angelegtAm",
"mobil",
"emailGesch",
"ePost",
"strasse",
"vorname",
"familienstand",
"betNr",
"geaendertVon",
"telGesch"
],
"suggested_mappings": [
[
"name",
"name"
],
[
"emailAddress",
"email"
]
]
}
}

View File

@@ -0,0 +1,323 @@
#!/usr/bin/env python3
"""
Helper-Script zum Vergleichen der Beteiligten-Strukturen zwischen Advoware und EspoCRM.
Usage:
python scripts/compare_beteiligte.py <entity_id_espocrm> [advoware_id]
Examples:
# Vergleiche EspoCRM Beteiligten (automatische Suche in Advoware)
python scripts/compare_beteiligte.py 64a3f2b8c9e1234567890abc
# Vergleiche mit spezifischer Advoware ID
python scripts/compare_beteiligte.py 64a3f2b8c9e1234567890abc 12345
"""
import sys
import asyncio
import json
import os
from pathlib import Path
# Add bitbylaw directory to path for imports
bitbylaw_dir = Path(__file__).parent.parent
sys.path.insert(0, str(bitbylaw_dir))
from services.espocrm import EspoCRMAPI
from services.advoware import AdvowareAPI
from config import Config
class SimpleContext:
"""Simple context for logging"""
class Logger:
def info(self, msg):
print(f"[INFO] {msg}")
def error(self, msg):
print(f"[ERROR] {msg}")
def debug(self, msg):
print(f"[DEBUG] {msg}")
def warning(self, msg):
print(f"[WARNING] {msg}")
def __init__(self):
self.logger = self.Logger()
async def fetch_from_espocrm(entity_id: str):
"""Fetch Beteiligter from EspoCRM"""
print("\n" + "="*80)
print("ESPOCRM - Fetching Beteiligter")
print("="*80)
context = SimpleContext()
espo = EspoCRMAPI(context=context)
try:
# Try different entity types that might contain Beteiligte
entity_types = ['CBeteiligte', 'Beteiligte', 'Contact', 'Account', 'Lead', 'CVmhErstgespraech', 'CVmhBeteiligte']
for entity_type in entity_types:
try:
print(f"\nTrying entity type: {entity_type}")
result = await espo.get_entity(entity_type, entity_id)
print(f"\n✓ Success! Found in {entity_type}")
print(f"\nEntity Structure:")
print("-" * 80)
print(json.dumps(result, indent=2, ensure_ascii=False))
return result
except Exception as e:
print(f" ✗ Not found in {entity_type}: {e}")
continue
print("\n✗ Entity not found in any known entity type")
return None
except Exception as e:
print(f"\n✗ Error fetching from EspoCRM: {e}")
return None
async def fetch_from_advoware(advoware_id: str = None, search_name: str = None):
"""Fetch Beteiligter from Advoware"""
print("\n" + "="*80)
print("ADVOWARE - Fetching Beteiligter")
print("="*80)
context = SimpleContext()
advo = AdvowareAPI(context=context)
try:
# Try to fetch by ID if provided
if advoware_id:
print(f"\nFetching by ID: {advoware_id}")
# Try correct Advoware endpoint
endpoints = [
f'/api/v1/advonet/Beteiligte/{advoware_id}',
]
for endpoint in endpoints:
try:
print(f" Trying endpoint: {endpoint}")
result = await advo.api_call(endpoint, method='GET')
if result:
# Advoware gibt oft Listen zurück, nehme erstes Element
if isinstance(result, list) and len(result) > 0:
result = result[0]
print(f"\n✓ Success! Found at {endpoint}")
print(f"\nEntity Structure:")
print("-" * 80)
print(json.dumps(result, indent=2, ensure_ascii=False))
return result
except Exception as e:
print(f" ✗ Not found at {endpoint}: {e}")
continue
# Try to search by name if EspoCRM data available
if search_name:
print(f"\nSearching by name: {search_name}")
search_endpoints = [
'/api/v1/advonet/Beteiligte',
]
for endpoint in search_endpoints:
try:
print(f" Trying endpoint: {endpoint}")
result = await advo.api_call(
endpoint,
method='GET',
params={'search': search_name, 'limit': 5}
)
if result and (isinstance(result, list) and len(result) > 0 or
isinstance(result, dict) and result.get('data')):
print(f"\n✓ Found {len(result) if isinstance(result, list) else len(result.get('data', []))} results")
print(f"\nSearch Results:")
print("-" * 80)
print(json.dumps(result, indent=2, ensure_ascii=False))
return result
except Exception as e:
print(f" ✗ Search failed at {endpoint}: {e}")
continue
print("\n✗ Entity not found in Advoware")
return None
except Exception as e:
print(f"\n✗ Error fetching from Advoware: {e}")
import traceback
traceback.print_exc()
return None
async def compare_structures(espo_data: dict, advo_data: dict):
"""Compare field structures between EspoCRM and Advoware"""
print("\n" + "="*80)
print("STRUCTURE COMPARISON")
print("="*80)
if not espo_data or not advo_data:
print("\n⚠ Cannot compare - missing data from one or both systems")
return
# Extract fields
espo_fields = set(espo_data.keys()) if isinstance(espo_data, dict) else set()
# Handle Advoware data structure (might be nested)
if isinstance(advo_data, dict):
if 'data' in advo_data:
advo_data = advo_data['data']
if isinstance(advo_data, list) and len(advo_data) > 0:
advo_data = advo_data[0]
advo_fields = set(advo_data.keys()) if isinstance(advo_data, dict) else set()
print(f"\nEspoCRM Fields ({len(espo_fields)}):")
print("-" * 40)
for field in sorted(espo_fields):
value = espo_data.get(field)
value_type = type(value).__name__
print(f" {field:<30} ({value_type})")
print(f"\nAdvoware Fields ({len(advo_fields)}):")
print("-" * 40)
for field in sorted(advo_fields):
value = advo_data.get(field)
value_type = type(value).__name__
print(f" {field:<30} ({value_type})")
# Find common fields (potential mappings)
common = espo_fields & advo_fields
espo_only = espo_fields - advo_fields
advo_only = advo_fields - espo_fields
print(f"\nCommon Fields ({len(common)}):")
print("-" * 40)
for field in sorted(common):
espo_val = espo_data.get(field)
advo_val = advo_data.get(field)
match = "" if espo_val == advo_val else ""
print(f" {match} {field}")
if espo_val != advo_val:
print(f" EspoCRM: {espo_val}")
print(f" Advoware: {advo_val}")
print(f"\nEspoCRM Only ({len(espo_only)}):")
print("-" * 40)
for field in sorted(espo_only):
print(f" {field}")
print(f"\nAdvoware Only ({len(advo_only)}):")
print("-" * 40)
for field in sorted(advo_only):
print(f" {field}")
# Suggest potential mappings based on field names
print(f"\nPotential Field Mappings:")
print("-" * 40)
mapping_suggestions = []
# Common name patterns
name_patterns = [
('name', 'name'),
('firstName', 'first_name'),
('lastName', 'last_name'),
('email', 'email'),
('emailAddress', 'email'),
('phone', 'phone'),
('phoneNumber', 'phone_number'),
('address', 'address'),
('street', 'street'),
('city', 'city'),
('postalCode', 'postal_code'),
('zipCode', 'postal_code'),
('country', 'country'),
]
for espo_field, advo_field in name_patterns:
if espo_field in espo_fields and advo_field in advo_fields:
mapping_suggestions.append((espo_field, advo_field))
print(f" {espo_field:<30}{advo_field}")
return {
'espo_fields': list(espo_fields),
'advo_fields': list(advo_fields),
'common': list(common),
'espo_only': list(espo_only),
'advo_only': list(advo_only),
'suggested_mappings': mapping_suggestions
}
async def main():
"""Main function"""
if len(sys.argv) < 2:
print(__doc__)
sys.exit(1)
espocrm_id = sys.argv[1]
advoware_id = sys.argv[2] if len(sys.argv) > 2 else None
print("\n" + "="*80)
print("BETEILIGTE STRUCTURE COMPARISON TOOL")
print("="*80)
print(f"\nEspoCRM Entity ID: {espocrm_id}")
if advoware_id:
print(f"Advoware ID: {advoware_id}")
# Check environment variables
print("\nEnvironment Check:")
print("-" * 40)
print(f"ESPOCRM_API_BASE_URL: {Config.ESPOCRM_API_BASE_URL}")
print(f"ESPOCRM_API_KEY: {'✓ Set' if Config.ESPOCRM_API_KEY else '✗ Missing'}")
print(f"ADVOWARE_API_BASE_URL: {Config.ADVOWARE_API_BASE_URL}")
print(f"ADVOWARE_API_KEY: {'✓ Set' if Config.ADVOWARE_API_KEY else '✗ Missing'}")
# Fetch from EspoCRM
espo_data = await fetch_from_espocrm(espocrm_id)
# Extract name for Advoware search
search_name = None
if espo_data:
search_name = (
espo_data.get('name') or
f"{espo_data.get('firstName', '')} {espo_data.get('lastName', '')}".strip() or
None
)
# Fetch from Advoware
advo_data = await fetch_from_advoware(advoware_id, search_name)
# Compare structures
if espo_data or advo_data:
comparison = await compare_structures(espo_data, advo_data)
# Save comparison to file
output_file = Path(__file__).parent / 'beteiligte_comparison_result.json'
with open(output_file, 'w', encoding='utf-8') as f:
json.dump({
'espocrm_data': espo_data,
'advoware_data': advo_data,
'comparison': comparison
}, f, indent=2, ensure_ascii=False)
print(f"\n\n{'='*80}")
print(f"Comparison saved to: {output_file}")
print(f"{'='*80}\n")
else:
print("\n⚠ No data available for comparison")
if __name__ == '__main__':
asyncio.run(main())