diff --git a/app/helpers/brevo.py b/app/helpers/brevo.py index afc30170..8de86818 100644 --- a/app/helpers/brevo.py +++ b/app/helpers/brevo.py @@ -39,13 +39,6 @@ class LinkCompanyContactData: contact_id: int -@dataclass -class UpdateDealStageData: - deal_id: str - pipeline_id: str - stage_id: str - - @dataclass class GetAllDealsByPipelineData: pipeline_id: str @@ -70,9 +63,12 @@ def __init__(self, api_key): self._configuration = sib_api_v3_sdk.Configuration() self._configuration.api_key["api-key"] = api_key self.api_key = api_key - self._api_instance = sib_api_v3_sdk.ContactsApi( - sib_api_v3_sdk.ApiClient(self._configuration) - ) + self._api_client = sib_api_v3_sdk.ApiClient(self._configuration) + self._contacts_api = sib_api_v3_sdk.ContactsApi(self._api_client) + self._deals_api = sib_api_v3_sdk.DealsApi(self._api_client) + + # Legacy: kept for backward compatibility with existing code + self._api_instance = self._contacts_api self._session = requests.Session() self._session.headers.update( @@ -175,24 +171,37 @@ def link_company_and_contact(self, data: LinkCompanyContactData): raise BrevoRequestError(f"Request to Brevo API failed: {e}") @check_api_key - def update_deal_stage(self, data: UpdateDealStageData): + def update_deal( + self, + deal_id: str, + pipeline_id: str = None, + stage_id: str = None, + attributes: dict = None, + ) -> None: + """Update a deal with stage and/or attributes in a single API call. + + Args: + deal_id: Brevo deal identifier + pipeline_id: Pipeline ID (required if updating stage) + stage_id: Stage ID (required if updating stage) + attributes: Additional attributes to update + """ try: - url = f"{self.BASE_URL}/crm/deals/{data.deal_id}" - payload = { - "attributes": { - "pipeline": data.pipeline_id, - "deal_stage": data.stage_id, - } - } - response = self._session.patch(url, json=payload) - response.raise_for_status() + payload_attributes = {} - if response.status_code == 204: - return {"message": "Deal stage updated successfully"} + if pipeline_id and stage_id: + payload_attributes["pipeline"] = pipeline_id + payload_attributes["deal_stage"] = stage_id + + if attributes: + payload_attributes.update(attributes) + + if not payload_attributes: + return + + body = {"attributes": payload_attributes} + self._deals_api.crm_deals_id_patch(id=deal_id, body=body) - return response.json() - except requests.exceptions.HTTPError as e: - self._handle_request_error(e) except ApiException as e: raise BrevoRequestError(f"Request to Brevo API failed: {e}") @@ -419,6 +428,26 @@ def get_existing_deals_by_pipeline(self, pipeline_id: str) -> list: "stage_id": deal_attrs.get("deal_stage"), "siren": deal_attrs.get("siren"), "siret": deal_attrs.get("siret"), + "phone_number": deal_attrs.get("phone_number"), + "nb_employees": deal_attrs.get("nb_employees"), + "stage_since_days": deal_attrs.get( + "stage_since_days" + ), + "total_employees_count": deal_attrs.get( + "total_employees_count" + ), + "invited_employees_count": deal_attrs.get( + "invited_employees_count" + ), + "invitation_percentage": deal_attrs.get( + "invitation_percentage" + ), + "validated_missions_count": deal_attrs.get( + "validated_missions_count" + ), + "active_employees_count": deal_attrs.get( + "active_employees_count" + ), } ) diff --git a/app/services/brevo/orchestrator.py b/app/services/brevo/orchestrator.py index 73baf4ad..10a77656 100644 --- a/app/services/brevo/orchestrator.py +++ b/app/services/brevo/orchestrator.py @@ -5,11 +5,7 @@ from typing import List, Dict, Any from dataclasses import dataclass, field -from app.helpers.brevo import ( - BrevoApiClient, - UpdateDealStageData, - BrevoRequestError, -) +from app.helpers.brevo import BrevoApiClient from .acquisition_data_finder import AcquisitionDataFinder from .activation_data_finder import ActivationDataFinder @@ -290,6 +286,34 @@ def _update_deal_identifier( else: deals_by_identifier[f"name_{company_name}"] = deal_info + def _build_deal_attributes( + self, company: Dict[str, Any] + ) -> Dict[str, Any]: + """Build attributes payload for deal update. + + Only includes fields that are present in company data. + Absent keys are ignored, but explicit None values are preserved + to allow clearing fields in Brevo. + """ + field_mappings = { + "siren": "siren", + "siret": "siret", + "phone_number": "phone_number", + "nb_employees": "nb_employees", + "stage_since_days": "stage_since_days", + "total_employees_count": "total_employees_count", + "invited_employees_count": "invited_employees_count", + "invitation_percentage": "invitation_percentage", + "validated_missions_count": "validated_missions_count", + "active_employees_count": "active_employees_count", + } + + return { + brevo_key: company[company_key] + for company_key, brevo_key in field_mappings.items() + if company_key in company + } + def _sync_single_company( self, company: Dict[str, Any], @@ -315,13 +339,21 @@ def _sync_single_company( ) if existing_deal: - if existing_deal["stage_id"] != target_stage_id: - update_data = UpdateDealStageData( + attributes = self._build_deal_attributes(company) + stage_changed = existing_deal["stage_id"] != target_stage_id + changed_attributes = { + key: value + for key, value in attributes.items() + if str(existing_deal.get(key)) != str(value) + } + + if stage_changed or changed_attributes: + self.brevo.update_deal( deal_id=existing_deal["id"], - pipeline_id=pipeline_id, - stage_id=target_stage_id, + pipeline_id=pipeline_id if stage_changed else None, + stage_id=target_stage_id if stage_changed else None, + attributes=changed_attributes or None, ) - self.brevo.update_deal_stage(update_data) result.updated_deals += 1 else: deal_id = self.brevo.create_deal_with_attributes(