Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 54 additions & 25 deletions app/helpers/brevo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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}")

Expand Down Expand Up @@ -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"
),
}
)

Expand Down
52 changes: 42 additions & 10 deletions app/services/brevo/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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],
Expand All @@ -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(
Expand Down