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 import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
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")
|
app = FastAPI(title="WhatsApp Central - Integrations Service")
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ app.add_middleware(
|
|||||||
)
|
)
|
||||||
|
|
||||||
app.include_router(odoo_router)
|
app.include_router(odoo_router)
|
||||||
|
app.include_router(sync_router)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
from app.routers.odoo import router as odoo_router
|
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.partner import PartnerService
|
||||||
from app.services.crm import CRMService
|
from app.services.crm import CRMService
|
||||||
from app.services.product import ProductService
|
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