- Added 5 core service modules:
* services/notification_utils.py: NotificationManager for manual actions (412 lines)
* services/advoware_service.py: Extended Advoware operations wrapper
* services/espocrm_mapper.py: BeteiligteMapper for data transformation (198 lines)
* services/bankverbindungen_mapper.py: BankverbindungenMapper (174 lines)
* services/beteiligte_sync_utils.py: BeteiligteSync with complex logic (663 lines)
- Distributed locking via Redis + syncStatus
- rowId-based change detection (primary) + timestamp fallback
- Retry with exponential backoff (1min, 5min, 15min, 1h, 4h)
- Conflict resolution (EspoCRM wins)
- Soft-delete handling & round-trip validation
- Added 2 event handler steps:
* steps/vmh/beteiligte_sync_event_step.py: Handles vmh.beteiligte.* queue events
- CREATE: New Beteiligte → POST to Advoware
- UPDATE: Bi-directional sync with conflict resolution
- DELETE: Not yet implemented
- SYNC_CHECK: Periodic sync check via cron
* steps/vmh/bankverbindungen_sync_event_step.py: Handles vmh.bankverbindungen.* events
- CREATE: New Bankverbindung → POST to Advoware
- UPDATE/DELETE: Send notification (API limitation - no PUT/DELETE support)
- Added pytz dependency to pyproject.toml (required for timezone handling)
System Status:
- 27 steps registered (14 VMH-specific)
- 8 queue subscriptions active (4 Beteiligte + 4 Bankverbindungen)
- All webhook endpoints operational and emitting queue events
- Sync handlers ready for processing
Note: Kommunikation sync (kommunikation_sync_utils.py) not yet migrated.
Complex bi-directional sync for Telefon/Email/Fax will be added in Phase 4.
Updated MIGRATION_STATUS.md
175 lines
5.5 KiB
Python
175 lines
5.5 KiB
Python
"""
|
|
EspoCRM ↔ Advoware Bankverbindungen Mapper
|
|
|
|
Transformiert Bankverbindungen zwischen den beiden Systemen
|
|
"""
|
|
|
|
from typing import Dict, Any, Optional, List
|
|
from datetime import datetime
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class BankverbindungenMapper:
|
|
"""Mapper für CBankverbindungen (EspoCRM) ↔ Bankverbindung (Advoware)"""
|
|
|
|
@staticmethod
|
|
def map_cbankverbindungen_to_advoware(espo_entity: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
Transformiert EspoCRM CBankverbindungen → Advoware Bankverbindung Format
|
|
|
|
Args:
|
|
espo_entity: CBankverbindungen Entity von EspoCRM
|
|
|
|
Returns:
|
|
Dict für Advoware API (POST/PUT /api/v1/advonet/Beteiligte/{id}/Bankverbindungen)
|
|
"""
|
|
logger.debug(f"Mapping EspoCRM → Advoware Bankverbindung: {espo_entity.get('id')}")
|
|
|
|
advo_data = {}
|
|
|
|
# Bankname
|
|
bank = espo_entity.get('bank')
|
|
if bank:
|
|
advo_data['bank'] = bank
|
|
|
|
# Kontonummer (deprecated, aber noch supported)
|
|
kto_nr = espo_entity.get('kontoNummer')
|
|
if kto_nr:
|
|
advo_data['ktoNr'] = kto_nr
|
|
|
|
# BLZ (deprecated, aber noch supported)
|
|
blz = espo_entity.get('blz')
|
|
if blz:
|
|
advo_data['blz'] = blz
|
|
|
|
# IBAN
|
|
iban = espo_entity.get('iban')
|
|
if iban:
|
|
advo_data['iban'] = iban
|
|
|
|
# BIC
|
|
bic = espo_entity.get('bic')
|
|
if bic:
|
|
advo_data['bic'] = bic
|
|
|
|
# Kontoinhaber
|
|
kontoinhaber = espo_entity.get('kontoinhaber')
|
|
if kontoinhaber:
|
|
advo_data['kontoinhaber'] = kontoinhaber
|
|
|
|
# SEPA Mandat
|
|
mandatsreferenz = espo_entity.get('mandatsreferenz')
|
|
if mandatsreferenz:
|
|
advo_data['mandatsreferenz'] = mandatsreferenz
|
|
|
|
mandat_vom = espo_entity.get('mandatVom')
|
|
if mandat_vom:
|
|
advo_data['mandatVom'] = mandat_vom
|
|
|
|
logger.debug(f"Mapped to Advoware: IBAN={advo_data.get('iban')}, Bank={advo_data.get('bank')}")
|
|
|
|
return advo_data
|
|
|
|
@staticmethod
|
|
def map_advoware_to_cbankverbindungen(advo_entity: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
Transformiert Advoware Bankverbindung → EspoCRM CBankverbindungen Format
|
|
|
|
Args:
|
|
advo_entity: Bankverbindung von Advoware API
|
|
|
|
Returns:
|
|
Dict für EspoCRM API (POST/PUT /api/v1/CBankverbindungen)
|
|
"""
|
|
logger.debug(f"Mapping Advoware → EspoCRM: id={advo_entity.get('id')}")
|
|
|
|
espo_data = {
|
|
'advowareId': advo_entity.get('id'), # Link zu Advoware
|
|
'advowareRowId': advo_entity.get('rowId'), # Änderungserkennung
|
|
}
|
|
|
|
# Bankname
|
|
bank = advo_entity.get('bank')
|
|
if bank:
|
|
espo_data['bank'] = bank
|
|
|
|
# Kontonummer
|
|
kto_nr = advo_entity.get('ktoNr')
|
|
if kto_nr:
|
|
espo_data['kontoNummer'] = kto_nr
|
|
|
|
# BLZ
|
|
blz = advo_entity.get('blz')
|
|
if blz:
|
|
espo_data['blz'] = blz
|
|
|
|
# IBAN
|
|
iban = advo_entity.get('iban')
|
|
if iban:
|
|
espo_data['iban'] = iban
|
|
|
|
# BIC
|
|
bic = advo_entity.get('bic')
|
|
if bic:
|
|
espo_data['bic'] = bic
|
|
|
|
# Kontoinhaber
|
|
kontoinhaber = advo_entity.get('kontoinhaber')
|
|
if kontoinhaber:
|
|
espo_data['kontoinhaber'] = kontoinhaber
|
|
|
|
# SEPA Mandat
|
|
mandatsreferenz = advo_entity.get('mandatsreferenz')
|
|
if mandatsreferenz:
|
|
espo_data['mandatsreferenz'] = mandatsreferenz
|
|
|
|
mandat_vom = advo_entity.get('mandatVom')
|
|
if mandat_vom:
|
|
# Konvertiere DateTime zu Date (EspoCRM Format: YYYY-MM-DD)
|
|
espo_data['mandatVom'] = mandat_vom.split('T')[0] if 'T' in mandat_vom else mandat_vom
|
|
|
|
logger.debug(f"Mapped to EspoCRM: IBAN={espo_data.get('iban')}")
|
|
|
|
# Entferne None-Werte (EspoCRM Validierung)
|
|
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 CBankverbindungen
|
|
advo_entity: Advoware Bankverbindung
|
|
|
|
Returns:
|
|
Liste von Feldnamen die unterschiedlich sind
|
|
"""
|
|
mapped_advo = BankverbindungenMapper.map_advoware_to_cbankverbindungen(advo_entity)
|
|
|
|
changed = []
|
|
|
|
compare_fields = [
|
|
'bank', 'iban', 'bic', 'kontoNummer', 'blz',
|
|
'kontoinhaber', 'mandatsreferenz', 'mandatVom',
|
|
'advowareId', '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
|