diff --git a/services/espocrm.py b/services/espocrm.py index 29279bc..d6104e9 100644 --- a/services/espocrm.py +++ b/services/espocrm.py @@ -331,6 +331,9 @@ class EspoCRMAPI: params=self._flatten_params(search_params) ) + # EspoCRM API-User limit: maxSize ≥ 500 → 403 Access forbidden + ESPOCRM_MAX_PAGE_SIZE = 200 + async def list_related( self, entity_type: str, @@ -343,9 +346,11 @@ class EspoCRMAPI: offset: int = 0, max_size: int = 50 ) -> Dict[str, Any]: + # Clamp max_size to avoid 403 from EspoCRM permission limit + safe_size = min(max_size, self.ESPOCRM_MAX_PAGE_SIZE) search_params: Dict[str, Any] = { 'offset': offset, - 'maxSize': max_size, + 'maxSize': safe_size, } if where: search_params['where'] = where @@ -362,6 +367,39 @@ class EspoCRMAPI: params=self._flatten_params(search_params) ) + async def list_related_all( + self, + entity_type: str, + entity_id: str, + link: str, + where: Optional[List[Dict]] = None, + select: Optional[str] = None, + order_by: Optional[str] = None, + order: Optional[str] = None, + ) -> List[Dict[str, Any]]: + """Fetch ALL related records via automatic pagination (safe page size).""" + page_size = self.ESPOCRM_MAX_PAGE_SIZE + offset = 0 + all_records: List[Dict[str, Any]] = [] + + while True: + result = await self.list_related( + entity_type, entity_id, link, + where=where, select=select, + order_by=order_by, order=order, + offset=offset, max_size=page_size + ) + page = result.get('list', []) + all_records.extend(page) + total = result.get('total', len(all_records)) + + if len(all_records) >= total or len(page) < page_size: + break + offset += page_size + + self._log(f"list_related_all {entity_type}/{entity_id}/{link}: {len(all_records)}/{total} records") + return all_records + async def create_entity( self, entity_type: str, diff --git a/src/steps/crm/akte/akte_sync_event_step.py b/src/steps/crm/akte/akte_sync_event_step.py index d323b81..01086d0 100644 --- a/src/steps/crm/akte/akte_sync_event_step.py +++ b/src/steps/crm/akte/akte_sync_event_step.py @@ -96,8 +96,7 @@ async def handler(event_data: Dict[str, Any], ctx: FlowContext) -> None: # ── Load CDokumente once (shared by Advoware + xAI sync) ───────────────── espo_docs: list = [] if advoware_enabled or xai_enabled: - docs_result = await espocrm.list_related('CAkten', akte_id, 'dokumentes', max_size=1000) - espo_docs = docs_result.get('list', []) + espo_docs = await espocrm.list_related_all('CAkten', akte_id, 'dokumentes') # ── ADVOWARE SYNC ──────────────────────────────────────────── advoware_results = None