Organized
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
"""High-level Redmine -> CRM sync orchestrator."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from redmine_crm.crm_client import CrmClient
|
||||
from redmine_crm.mapper import enrich, map_contact
|
||||
from redmine_crm.redmine_client import RedmineClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SyncState:
|
||||
"""Minimal sync state tracker."""
|
||||
|
||||
def __init__(self, storage: Optional[Dict[str, Any]] = None) -> None:
|
||||
self._data = storage if storage is not None else {}
|
||||
|
||||
def get_last_sync(self) -> Optional[str]:
|
||||
return self._data.get("last_sync_timestamp")
|
||||
|
||||
def update(self, timestamp: str, stats: Dict[str, Any]) -> None:
|
||||
self._data["last_sync_timestamp"] = timestamp
|
||||
self._data["last_run_stats"] = stats
|
||||
|
||||
|
||||
def run_sync(
|
||||
redmine: RedmineClient,
|
||||
crm: CrmClient,
|
||||
sync_state: SyncState,
|
||||
*,
|
||||
base_url: str,
|
||||
batch_size: int = 100,
|
||||
) -> Dict[str, Any]:
|
||||
"""Run the full ETL pipeline: Extract, Transform, Load."""
|
||||
last_sync = sync_state.get_last_sync()
|
||||
logger.info("Starting Redmine -> CRM sync; last_sync=%s", last_sync)
|
||||
|
||||
# Extract
|
||||
contacts = redmine.iter_all_contacts(
|
||||
updated_since=last_sync,
|
||||
batch_size=batch_size,
|
||||
)
|
||||
logger.info("Extracted %d Redmine contacts", len(contacts))
|
||||
|
||||
# Transform
|
||||
records: List[Dict[str, Any]] = []
|
||||
for contact in contacts:
|
||||
try:
|
||||
mapped = map_contact(contact, base_url=base_url)
|
||||
enriched, _enrich_meta = enrich(mapped, contact)
|
||||
records.append(enriched)
|
||||
except Exception as exc:
|
||||
logger.exception("Failed to map Redmine contact %s: %s", contact.get("id"), exc)
|
||||
|
||||
# Load
|
||||
stats = crm.sync_batch(records)
|
||||
logger.info("CRM upsert stats: %s", stats)
|
||||
|
||||
import datetime
|
||||
|
||||
sync_state.update(datetime.datetime.now(datetime.timezone.utc).isoformat(), stats)
|
||||
return stats
|
||||
Reference in New Issue
Block a user