# Entity-Mapping: EspoCRM CBeteiligte ↔ Advoware Beteiligte

Basierend auf dem Vergleich von:
- **EspoCRM**: CBeteiligte Entity ID `68e4af00172be7924`
- **Advoware**: Beteiligter ID `104860`

## Gemeinsame Felder (direkte Übereinstimmung)

| EspoCRM Feld | Advoware Feld | Typ | Notes |
|--------------|---------------|-----|-------|
| `name` | `name` | string | Vollständiger Name |
| `rechtsform` | `rechtsform` | string | Rechtsform (z.B. "GmbH", "Frau") |
| `id` | `id` | mixed | **Achtung:** EspoCRM=string, Advoware=int |

## Namenfelder

| EspoCRM Feld | Advoware Feld | Mapping |
|--------------|---------------|---------|
| `firstName` | `vorname` | ✓ Direkt |
| `lastName` | `name` | ✓ Bei Personen |
| `middleName` | - | ❌ Kein direktes Mapping |
| `firmenname` | `name` | ✓ Bei Firmen |
| - | `geburtsname` | ← Nur in Advoware |
| - | `kurzname` | ← Nur in Advoware |

## Kontaktdaten

| EspoCRM Feld | Advoware Feld | Mapping |
|--------------|---------------|---------|
| `emailAddress` | `emailGesch` | ✓ Geschäftlich |
| `emailAddressData` (array) | `email` | ⚠️ Komplex: Array vs. String |
| `phoneNumber` | `telGesch` | ✓ Geschäftstelefon |
| `phoneNumberData` (array) | `telPrivat` | ⚠️ Komplex |
| - | `mobil` | ← Nur in Advoware |
| - | `faxGesch` / `faxPrivat` | ← Nur in Advoware |
| - | `autotelefon` | ← Nur in Advoware |
| - | `internet` | ← Nur in Advoware |

**Hinweis**: Advoware hat zusätzlich `kommunikation` Array mit strukturierten Kontaktdaten.

## Adressdaten

| EspoCRM Feld | Advoware Feld | Mapping |
|--------------|---------------|---------|
| `adressensIds` / `adressensNames` | `adressen` (array) | ⚠️ Beziehung |
| - | `strasse` | ← Hauptadresse in Advoware Root |
| - | `plz` | ← Hauptadresse in Advoware Root |
| - | `ort` | ← Hauptadresse in Advoware Root |
| - | `anschrift` | ← Formatierte Adresse |

**Hinweis**: 
- EspoCRM: Adressen als Related Entities (IDs/Names)
- Advoware: Hauptadresse im Root-Objekt + `adressen` Array für zusätzliche

## Anrede & Titel

| EspoCRM Feld | Advoware Feld | Mapping |
|--------------|---------------|---------|
| `salutationName` | `anrede` | ✓ (z.B. "Frau", "Herr") |
| - | `bAnrede` | ← Briefanrede ("Sehr geehrte...") |
| - | `titel` | ← Titel (Dr., Prof., etc.) |
| - | `zusatz` | ← Namenszusatz |

## Geburtsdaten

| EspoCRM Feld | Advoware Feld | Mapping |
|--------------|---------------|---------|
| `dateOfBirth` | `geburtsdatum` | ✓ Direkt |
| - | `sterbedatum` | ← Nur in Advoware |
| - | `familienstand` | ← Nur in Advoware |

## Handelsregister (für Firmen)

| EspoCRM Feld | Advoware Feld | Mapping |
|--------------|---------------|---------|
| `handelsregisterNummer` | `handelsRegisterNummer` | ✓ Direkt |
| `handelsregisterArt` (z.B. "HRB") | - | ❌ Nur in EspoCRM |
| - | `registergericht` | ← Nur in Advoware |

## Bankverbindungen

| EspoCRM Feld | Advoware Feld | Mapping |
|--------------|---------------|---------|
| `bankverbindungensIds` / Names | `bankkverbindungen` (array) | ⚠️ Related Entity vs. Array |

## Beteiligungen/Akten

| EspoCRM Feld | Advoware Feld | Mapping |
|--------------|---------------|---------|
| - | `beteiligungen` (array) | ← Nur in Advoware |

**Hinweis**: Advoware speichert die Akten-Beteiligungen direkt beim Beteiligten.

## EspoCRM-spezifische Felder

| Feld | Zweck |
|------|-------|
| `betnr` | Beteiligten-Nummer (= Advoware `betNr`) |
| `advowareLastSync` | Zeitstempel der letzten Synchronisation |
| `syncStatus` | Status: "clean", "dirty", "syncing" |
| `disgTyp` | DISC-Persönlichkeitstyp |
| `description` | Notizen/Beschreibung |
| `createdAt` / `createdById` / `createdByName` | Audit-Felder |
| `modifiedAt` / `modifiedById` / `modifiedByName` | Audit-Felder |
| `assignedUserId` / `assignedUserName` | Zuweisungen |
| `teamsIds` / `teamsNames` | Team-Zugehörigkeit |
| `deleted` | Soft-Delete Flag |
| `isFollowed` / `followersIds` | Social Features |

## Advoware-spezifische Felder

| Feld | Zweck |
|------|-------|
| `betNr` | Interne Beteiligten-Nummer |
| `rowId` | Datenbank Row-ID |
| `art` | Beteiligten-Art |
| `angelegtAm` / `angelegtVon` | Erstellt |
| `geaendertAm` / `geaendertVon` | Geändert |
| `kontaktpersonen` (array) | Kontaktpersonen bei Firmen |
| `ePost` / `bea` | Spezielle Kommunikationskanäle |

## Mapping-Strategie

### 1. Person (Natürliche Person)

```python
espocrm_to_advoware = {
    'firstName': 'vorname',
    'lastName': 'name',
    'dateOfBirth': 'geburtsdatum',
    'rechtsform': 'rechtsform',  # z.B. "Herr", "Frau"
    'salutationName': 'anrede',
    'emailAddress': 'emailGesch',
    'phoneNumber': 'telGesch',
}
```

### 2. Firma (Juristische Person)

```python
espocrm_to_advoware = {
    'firmenname': 'name',
    'rechtsform': 'rechtsform',  # z.B. "GmbH", "AG"
    'handelsregisterNummer': 'handelsRegisterNummer',
    'emailAddress': 'emailGesch',
    'phoneNumber': 'telGesch',
}
```

### 3. Adressen

**EspoCRM → Advoware**:
- Lade Related Entity `Adressen` via `adressensIds`
- Mappe Hauptadresse zu Root-Feldern `strasse`, `plz`, `ort`
- Zusätzliche Adressen in `adressen` Array

**Advoware → EspoCRM**:
- Hauptadresse aus Root-Feldern
- `adressen` Array → Related Entities in EspoCRM

### 4. Kontaktdaten (Komplex)

**EspoCRM `emailAddressData`**:
```json
[
  {
    "emailAddress": "primary@example.com",
    "primary": true,
    "optOut": false,
    "invalid": false
  }
]
```

**Advoware `kommunikation`**:
```json
[
  {
    "id": 88002,
    "kommArt": 0,  // 0=Telefon, 1=Email, etc.
    "tlf": "0511/12345-60",
    "online": false
  }
]
```

**Mapping**: Erfordert Transformation basierend auf `kommArt`.

## Sync-Richtungen

### EspoCRM → Advoware (Webhook-getrieben)

1. Webhook empfängt `CBeteiligte` create/update/delete
2. Mappe Felder gemäß Tabelle oben
3. `POST /api/v1/advonet/Beteiligte` (create) oder
   `PUT /api/v1/advonet/Beteiligte/{betNr}` (update)
4. Update `advowareLastSync` und `syncStatus` in EspoCRM

### Advoware → EspoCRM (Polling oder Webhook)

1. Überwache Änderungen in Advoware
2. Mappe Felder zurück
3. `PUT /api/v1/CBeteiligte/{id}` in EspoCRM
4. Setze `syncStatus = "clean"`

## Konflikte & Regeln

| Szenario | Regel |
|----------|-------|
| Beide Systeme geändert | Advoware als Master (führendes System) |
| Feld nur in EspoCRM | Ignorieren beim Export, behalten |
| Feld nur in Advoware | Null/Leer in EspoCRM setzen |
| `betnr` vs. `betNr` | Sync-Link: Muss identisch sein |

## ID-Mapping

**Problem**: EspoCRM und Advoware haben unterschiedliche ID-Systeme.

**Lösung**:
- EspoCRM `betnr` Feld = Advoware `betNr`
- Dies ist der Sync-Link zwischen beiden Systemen
- Bei Create in EspoCRM: `betnr` erst nach Advoware-Insert setzen
- Bei Create in Advoware: EspoCRM ID in Custom Field speichern?

## Nächste Schritte

1. **Mapper-Modul erstellen**: `bitbylaw/services/espocrm_mapper.py`
   - `map_cbeteiligte_to_advoware(espo_data) -> advo_data`
   - `map_advoware_to_cbeteiligte(advo_data) -> espo_data`

2. **Sync-Event-Step implementieren**: `bitbylaw/steps/vmh/beteiligte_sync_event_step.py`
   - Subscribe to `vmh.beteiligte.create/update/delete`
   - Fetch full entity from EspoCRM
   - Transform via Mapper
   - Write to Advoware
   - Update sync metadata

3. **Testing**:
   - Unit Tests für Mapper
   - Integration Tests mit Sandbox-Daten
   - Konflikt-Szenarien testen

4. **Error Handling**:
   - Retry-Logic bei API-Fehlern
   - Validation vor dem Sync
   - Rollback bei Fehlern?
   - Logging aller Sync-Operationen

5. **Performance**:
   - Batch-Processing für mehrere Beteiligte
   - Rate Limiting beachten
   - Caching von Lookup-Daten

## Beispiel-Transformation

### EspoCRM CBeteiligte:
```json
{
  "id": "68e4af00172be7924",
  "firstName": "Angela",
  "lastName": "Mustermanns",
  "rechtsform": "Frau",
  "emailAddress": "angela@example.com",
  "phoneNumber": "0511/12345",
  "betnr": 104860,
  "handelsregisterNummer": null
}
```

### Advoware Beteiligter:
```json
{
  "betNr": 104860,
  "vorname": "Angela",
  "name": "Mustermanns",
  "rechtsform": "Frau",
  "anrede": "Frau",
  "emailGesch": "angela@example.com",
  "telGesch": "0511/12345"
}
```

---

**Generiert am**: 2026-02-07
**Basierend auf**: Real-Daten-Vergleich mit `scripts/compare_beteiligte.py`
