feat(integrations): add contact sync service
Add bidirectional contact synchronization between WhatsApp Central and Odoo, including sync endpoints and ContactSyncService. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from app.routers import odoo_router
|
||||
from app.routers import odoo_router, sync_router
|
||||
|
||||
app = FastAPI(title="WhatsApp Central - Integrations Service")
|
||||
|
||||
@@ -13,6 +13,7 @@ app.add_middleware(
|
||||
)
|
||||
|
||||
app.include_router(odoo_router)
|
||||
app.include_router(sync_router)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from app.routers.odoo import router as odoo_router
|
||||
from app.routers.sync import router as sync_router
|
||||
|
||||
__all__ = ["odoo_router"]
|
||||
__all__ = ["odoo_router", "sync_router"]
|
||||
|
||||
48
services/integrations/app/routers/sync.py
Normal file
48
services/integrations/app/routers/sync.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from app.services.sync import ContactSyncService
|
||||
|
||||
router = APIRouter(prefix="/api/sync", tags=["sync"])
|
||||
|
||||
|
||||
class SyncContactRequest(BaseModel):
|
||||
contact_id: str
|
||||
phone: str
|
||||
name: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
|
||||
|
||||
@router.post("/contact-to-odoo")
|
||||
async def sync_contact_to_odoo(request: SyncContactRequest):
|
||||
"""Sync WhatsApp contact to Odoo partner"""
|
||||
try:
|
||||
service = ContactSyncService()
|
||||
partner_id = await service.sync_contact_to_odoo(
|
||||
contact_id=request.contact_id,
|
||||
phone=request.phone,
|
||||
name=request.name,
|
||||
email=request.email,
|
||||
)
|
||||
|
||||
if partner_id:
|
||||
return {"success": True, "odoo_partner_id": partner_id}
|
||||
raise HTTPException(500, "Failed to sync contact")
|
||||
except Exception as e:
|
||||
raise HTTPException(500, str(e))
|
||||
|
||||
|
||||
@router.post("/partner-to-contact/{partner_id}")
|
||||
async def sync_partner_to_contact(partner_id: int):
|
||||
"""Sync Odoo partner to WhatsApp contact"""
|
||||
try:
|
||||
service = ContactSyncService()
|
||||
contact_id = await service.sync_partner_to_contact(partner_id)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"contact_id": contact_id,
|
||||
"message": "Contact found" if contact_id else "No matching contact",
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(500, str(e))
|
||||
@@ -1,5 +1,6 @@
|
||||
from app.services.partner import PartnerService
|
||||
from app.services.crm import CRMService
|
||||
from app.services.product import ProductService
|
||||
from app.services.sync import ContactSyncService
|
||||
|
||||
__all__ = ["PartnerService", "CRMService", "ProductService"]
|
||||
__all__ = ["PartnerService", "CRMService", "ProductService", "ContactSyncService"]
|
||||
|
||||
87
services/integrations/app/services/sync.py
Normal file
87
services/integrations/app/services/sync.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from typing import Optional
|
||||
import httpx
|
||||
from app.config import get_settings
|
||||
from app.services.partner import PartnerService
|
||||
from app.schemas.partner import PartnerCreate
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
|
||||
class ContactSyncService:
|
||||
"""Sync contacts between WhatsApp Central and Odoo"""
|
||||
|
||||
def __init__(self):
|
||||
self.partner_service = PartnerService()
|
||||
|
||||
async def sync_contact_to_odoo(
|
||||
self,
|
||||
contact_id: str,
|
||||
phone: str,
|
||||
name: str = None,
|
||||
email: str = None,
|
||||
) -> Optional[int]:
|
||||
"""
|
||||
Sync a WhatsApp contact to Odoo.
|
||||
Returns Odoo partner_id.
|
||||
"""
|
||||
# Check if partner exists
|
||||
partner = self.partner_service.search_by_phone(phone)
|
||||
|
||||
if partner:
|
||||
return partner.id
|
||||
|
||||
# Create new partner
|
||||
data = PartnerCreate(
|
||||
name=name or phone,
|
||||
mobile=phone,
|
||||
email=email,
|
||||
)
|
||||
partner_id = self.partner_service.create(data)
|
||||
|
||||
# Update contact in API Gateway with odoo_partner_id
|
||||
await self._update_contact_odoo_id(contact_id, partner_id)
|
||||
|
||||
return partner_id
|
||||
|
||||
async def _update_contact_odoo_id(self, contact_id: str, odoo_id: int):
|
||||
"""Update contact's odoo_partner_id in API Gateway"""
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
await client.patch(
|
||||
f"{settings.API_GATEWAY_URL}/api/internal/contacts/{contact_id}",
|
||||
json={"odoo_partner_id": odoo_id},
|
||||
timeout=10,
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Failed to update contact odoo_id: {e}")
|
||||
|
||||
async def sync_partner_to_contact(self, partner_id: int) -> Optional[str]:
|
||||
"""
|
||||
Sync Odoo partner to WhatsApp contact.
|
||||
Returns contact_id if found.
|
||||
"""
|
||||
partner = self.partner_service.get_by_id(partner_id)
|
||||
|
||||
if not partner.phone and not partner.mobile:
|
||||
return None
|
||||
|
||||
phone = partner.mobile or partner.phone
|
||||
|
||||
# Search contact in API Gateway
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(
|
||||
f"{settings.API_GATEWAY_URL}/api/internal/contacts/search",
|
||||
params={"phone": phone},
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
contact = response.json()
|
||||
if not contact.get("odoo_partner_id"):
|
||||
await self._update_contact_odoo_id(contact["id"], partner_id)
|
||||
return contact["id"]
|
||||
except Exception as e:
|
||||
print(f"Failed to search contact: {e}")
|
||||
|
||||
return None
|
||||
Reference in New Issue
Block a user