Files
motia-iii/src/steps/crm/akte/akte_sync_cron_step.py

128 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Akte Sync - Cron Poller
Polls the Advoware Watcher Redis Sorted Set every 10 seconds (10 s debounce):
advoware:pending_aktennummern written by Windows Advoware Watcher
{ aktennummer → timestamp }
Eligibility (either flag triggers sync):
syncSchalter AND aktivierungsstatus in valid list → Advoware sync
aiAktivierungsstatus in valid list → xAI sync
EspoCRM webhooks emit akte.sync directly (no queue needed).
Failed akte.sync events are retried by Motia automatically.
"""
from motia import FlowContext, cron
config = {
"name": "Akte Sync - Cron Poller",
"description": "Poll Redis for pending Aktennummern and emit akte.sync events (10 s debounce)",
"flows": ["akte-sync"],
"triggers": [cron("*/10 * * * * *")],
"enqueues": ["akte.sync"],
}
# Queue 1: written by Windows Advoware Watcher (keyed by Aktennummer)
PENDING_ADVO_KEY = "advoware:pending_aktennummern"
PROCESSING_ADVO_KEY = "advoware:processing_aktennummern"
DEBOUNCE_SECS = 10
BATCH_SIZE = 5 # max items to process per cron tick
VALID_ADVOWARE_STATUSES = frozenset({'import', 'new', 'active'})
VALID_AI_STATUSES = frozenset({'new', 'active'})
async def handler(input_data: None, ctx: FlowContext) -> None:
import time
from services.redis_client import get_redis_client
from services.espocrm import EspoCRMAPI
ctx.logger.info("=" * 60)
ctx.logger.info("⏰ AKTE CRON POLLER")
redis_client = get_redis_client(strict=False)
if not redis_client:
ctx.logger.error("❌ Redis unavailable")
ctx.logger.info("=" * 60)
return
espocrm = EspoCRMAPI(ctx)
cutoff = time.time() - DEBOUNCE_SECS
advo_pending = redis_client.zcard(PENDING_ADVO_KEY)
ctx.logger.info(f" Pending (aktennr) : {advo_pending}")
processed_count = 0
# ── Queue: Advoware Watcher (by Aktennummer) ───────────────────────
advo_entries = redis_client.zrangebyscore(PENDING_ADVO_KEY, min=0, max=cutoff, start=0, num=BATCH_SIZE)
for raw in advo_entries:
aktennr = raw.decode() if isinstance(raw, bytes) else raw
score = redis_client.zscore(PENDING_ADVO_KEY, aktennr) or 0
age = time.time() - score
redis_client.zrem(PENDING_ADVO_KEY, aktennr)
redis_client.sadd(PROCESSING_ADVO_KEY, aktennr)
processed_count += 1
ctx.logger.info(f"📋 Aktennummer: {aktennr} (age={age:.1f}s)")
try:
result = await espocrm.list_entities(
'CAkten',
where=[{'type': 'equals', 'attribute': 'aktennummer', 'value': int(aktennr)}],
max_size=1,
)
if not result or not result.get('list'):
ctx.logger.warn(f"⚠️ No CAkten found for aktennummer={aktennr} removing")
else:
akte = result['list'][0]
await _emit_if_eligible(akte, aktennr, ctx)
except Exception as e:
ctx.logger.error(f"❌ Error (aktennr queue) {aktennr}: {e}")
redis_client.zadd(PENDING_ADVO_KEY, {aktennr: time.time()})
finally:
redis_client.srem(PROCESSING_ADVO_KEY, aktennr)
if not processed_count:
if advo_pending > 0:
ctx.logger.info(f"⏸️ Entries pending but all too recent (< {DEBOUNCE_SECS}s)")
else:
ctx.logger.info("✓ Queue empty")
else:
ctx.logger.info(f"✓ Processed {processed_count} item(s)")
ctx.logger.info("=" * 60)
async def _emit_if_eligible(akte: dict, aktennr, ctx: FlowContext) -> None:
"""Check eligibility and emit akte.sync if applicable."""
akte_id = akte['id']
# Prefer aktennr from argument; fall back to entity field
aktennummer = aktennr or akte.get('aktennummer')
sync_schalter = akte.get('syncSchalter', False)
aktivierungsstatus = str(akte.get('aktivierungsstatus') or '').lower()
ai_status = str(akte.get('aiAktivierungsstatus') or '').lower()
advoware_eligible = bool(aktennummer) and sync_schalter and aktivierungsstatus in VALID_ADVOWARE_STATUSES
xai_eligible = ai_status in VALID_AI_STATUSES
ctx.logger.info(f" akte_id : {akte_id}")
ctx.logger.info(f" aktennummer : {aktennummer or ''}")
ctx.logger.info(f" aktivierungsstatus : {aktivierungsstatus} ({'' if advoware_eligible else '⏭️'})")
ctx.logger.info(f" aiAktivierungsstatus : {ai_status} ({'' if xai_eligible else '⏭️'})")
if not advoware_eligible and not xai_eligible:
ctx.logger.warn(f"⚠️ Akte {akte_id} not eligible for any sync")
return
await ctx.enqueue({
'topic': 'akte.sync',
'data': {
'akte_id': akte_id,
'aktennummer': aktennummer, # may be None for xAI-only Akten
},
})
ctx.logger.info(f"📤 akte.sync emitted (akte_id={akte_id}, aktennummer={aktennummer or ''})")