""" EspoCRM ↔ Advoware Entity Mapper Transformiert Beteiligte zwischen den beiden Systemen basierend auf ENTITY_MAPPING_CBeteiligte_Advoware.md """ from typing import Dict, Any, Optional, List from datetime import datetime import logging from services.models import ( AdvowareBeteiligteCreate, AdvowareBeteiligteUpdate, EspoCRMBeteiligteCreate, validate_beteiligte_advoware, validate_beteiligte_espocrm ) from services.exceptions import ValidationError from services.config import FEATURE_FLAGS class BeteiligteMapper: """Mapper für CBeteiligte (EspoCRM) ↔ Beteiligte (Advoware)""" @staticmethod def map_cbeteiligte_to_advoware(espo_entity: Dict[str, Any]) -> Dict[str, Any]: """ Transformiert EspoCRM CBeteiligte → Advoware Beteiligte Format (STAMMDATEN) WICHTIG: Kontaktdaten (Telefon, Email, Fax, Bankverbindungen) werden über separate Advoware-Endpoints gesynct und sind NICHT Teil dieser Mapping-Funktion. Args: espo_entity: CBeteiligte Entity von EspoCRM Returns: Dict mit Stammdaten für Advoware API (POST/PUT /api/v1/advonet/Beteiligte) Raises: ValidationError: Bei Validierungsfehlern (wenn strict_validation aktiviert) """ logger.debug(f"Mapping EspoCRM → Advoware STAMMDATEN: {espo_entity.get('id')}") # Bestimme ob Person oder Firma (über firmenname-Feld) firmenname = espo_entity.get('firmenname') is_firma = bool(firmenname and firmenname.strip()) # Basis-Struktur (nur die funktionierenden Felder!) advo_data = { 'rechtsform': espo_entity.get('rechtsform', ''), } # NAME: Person vs. Firma if is_firma: # Firma: Lese von firmenname-Feld advo_data['name'] = firmenname advo_data['vorname'] = None else: # Natürliche Person: Lese von lastName/firstName advo_data['name'] = espo_entity.get('lastName', '') advo_data['vorname'] = espo_entity.get('firstName', '') # ANREDE & TITEL (funktionierende Felder) salutation = espo_entity.get('salutationName') if salutation: advo_data['anrede'] = salutation titel = espo_entity.get('titel') if titel: advo_data['titel'] = titel # BRIEFANREDE (bAnrede) brief_anrede = espo_entity.get('briefAnrede') if brief_anrede: advo_data['bAnrede'] = brief_anrede # ZUSATZ zusatz = espo_entity.get('zusatz') if zusatz: advo_data['zusatz'] = zusatz # GEBURTSDATUM date_of_birth = espo_entity.get('dateOfBirth') if date_of_birth: advo_data['geburtsdatum'] = date_of_birth # HINWEIS: handelsRegisterNummer und registergericht funktionieren NICHT! # Advoware ignoriert diese Felder im PUT (trotz Swagger Schema) logger.debug(f"Mapped to Advoware STAMMDATEN: name={advo_data.get('name')}, vorname={advo_data.get('vorname')}, rechtsform={advo_data.get('rechtsform')}") # Optional: Validiere mit Pydantic wenn aktiviert if FEATURE_FLAGS.strict_validation: try: validate_beteiligte_advoware(advo_data) except ValidationError as e: logger.warning(f"Validation warning: {e}") # Continue anyway - validation ist optional return advo_data @staticmethod def map_advoware_to_cbeteiligte(advo_entity: Dict[str, Any]) -> Dict[str, Any]: """ Transformiert Advoware Beteiligte → EspoCRM CBeteiligte Format Args: advo_entity: Beteiligter von Advoware API Returns: Dict für EspoCRM API (POST/PUT /api/v1/CBeteiligte) """ logger.debug(f"Mapping Advoware → EspoCRM: betNr={advo_entity.get('betNr')}") # Bestimme ob Person oder Firma vorname = advo_entity.get('vorname') is_person = bool(vorname) # Basis-Struktur espo_data = { 'rechtsform': advo_entity.get('rechtsform', ''), 'betnr': advo_entity.get('betNr'), # Link zu Advoware 'advowareRowId': advo_entity.get('rowId'), # Änderungserkennung } # NAME: Person vs. Firma (EspoCRM blendet lastName/firstName aus bei Firmen) if is_person: # Natürliche Person → lastName/firstName verwenden espo_data['firstName'] = vorname espo_data['lastName'] = advo_entity.get('name', '') espo_data['name'] = f"{vorname} {advo_entity.get('name', '')}".strip() espo_data['firmenname'] = None # Firma-Feld leer lassen else: # Firma → firmenname verwenden (EspoCRM zeigt dann nur dieses Feld) firma_name = advo_entity.get('name', '') espo_data['firmenname'] = firma_name espo_data['name'] = firma_name # lastName/firstName nicht setzen (EspoCRM blendet sie aus bei Firmen) espo_data['firstName'] = None espo_data['lastName'] = None # ANREDE & TITEL anrede = advo_entity.get('anrede') if anrede: espo_data['salutationName'] = anrede titel = advo_entity.get('titel') if titel: espo_data['titel'] = titel # BRIEFANREDE b_anrede = advo_entity.get('bAnrede') if b_anrede: espo_data['briefAnrede'] = b_anrede # ZUSATZ zusatz = advo_entity.get('zusatz') if zusatz: espo_data['zusatz'] = zusatz # GEBURTSDATUM (nur Datum-Teil ohne Zeit) geburtsdatum = advo_entity.get('geburtsdatum') if geburtsdatum: # Advoware gibt '2001-01-05T00:00:00', EspoCRM will nur '2001-01-05' espo_data['dateOfBirth'] = geburtsdatum.split('T')[0] if 'T' in geburtsdatum else geburtsdatum logger.debug(f"Mapped to EspoCRM STAMMDATEN: name={espo_data.get('name')}") # WICHTIG: Entferne None-Werte (EspoCRM mag keine expliziten None bei required fields) espo_data = {k: v for k, v in espo_data.items() if v is not None} return espo_data @staticmethod def get_changed_fields(espo_entity: Dict[str, Any], advo_entity: Dict[str, Any]) -> List[str]: """ Vergleicht zwei Entities und gibt Liste der geänderten Felder zurück Args: espo_entity: EspoCRM CBeteiligte advo_entity: Advoware Beteiligte Returns: Liste von Feldnamen die unterschiedlich sind """ # Mappe Advoware zu EspoCRM Format für Vergleich mapped_advo = BeteiligteMapper.map_advoware_to_cbeteiligte(advo_entity) changed = [] # Vergleiche wichtige Felder compare_fields = [ 'name', 'firstName', 'lastName', 'firmenname', 'emailAddress', 'phoneNumber', 'dateOfBirth', 'rechtsform', 'handelsregisterNummer', 'handelsregisterArt', 'registergericht', 'betnr', 'advowareRowId' ] for field in compare_fields: espo_val = espo_entity.get(field) advo_val = mapped_advo.get(field) # Normalisiere None und leere Strings espo_val = espo_val if espo_val else None advo_val = advo_val if advo_val else None if espo_val != advo_val: changed.append(field) logger.debug(f"Field '{field}' changed: EspoCRM='{espo_val}' vs Advoware='{advo_val}'") return changed