This commit is contained in:
debu-2 2026-10-14 06:04:27 +05:30
parent 84544e1153
commit a109df2deb
7 changed files with 51 additions and 11 deletions

View file

@ -227,6 +227,11 @@ def _serialize_phone(phone: WhatsAppPhoneAsset) -> dict[str, Any]:
}
def meta_calls_allowed(account: WhatsAppBusinessAccount) -> bool:
"""A disconnected account keeps its saved records and is not sent to Meta."""
return account.status != "disconnected"
def serialize_account(account: WhatsAppBusinessAccount) -> dict[str, Any]:
phones = [_serialize_phone(phone) for phone in account.phones]
primary = next((phone for phone in phones if phone["status"] == "connected"), phones[0] if phones else None)
@ -906,6 +911,8 @@ async def connect_with_token(
async def refresh_account(db: Session, user: UserAccount, account_id: Any) -> dict[str, Any]:
account = _owned_account(db, user, account_id)
if not meta_calls_allowed(account):
return serialize_account(account)
connection = account.connection
token = connection.access_token if connection else None
if connection is None or not token or connection.status == "disconnected":
@ -933,6 +940,8 @@ async def register_phone_number(
pin: str | None,
) -> dict[str, Any]:
account = _owned_account(db, user, account_id)
if not meta_calls_allowed(account):
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Reconnect this WhatsApp account before registering the number")
connection = account.connection
token = connection.access_token if connection else None
if connection is None or not token or connection.status == "disconnected":
@ -970,6 +979,8 @@ async def register_phone_number(
async def disconnect_account(db: Session, user: UserAccount, account_id: Any) -> dict[str, Any]:
account = _owned_account(db, user, account_id)
if not meta_calls_allowed(account):
return serialize_account(account)
connection = account.connection
token = connection.access_token if connection else None
if token:
@ -1012,7 +1023,7 @@ def account_detail(db: Session, user: UserAccount, account_id: Any) -> dict[str,
async def list_templates(db: Session, user: UserAccount, account_id: Any) -> dict[str, Any]:
account = _owned_account(db, user, account_id)
if account.templates_status != "active" or account.connection is None or not account.connection.access_token:
if not meta_calls_allowed(account) or account.templates_status != "active" or account.connection is None or not account.connection.access_token:
return {"available": False, "templates": []}
data = await _graph(
"GET",
@ -1054,6 +1065,8 @@ async def create_marketing_template(
) -> dict[str, Any]:
"""Submit a marketing template to Meta for the connected WhatsApp account."""
account = _owned_account(db, user, account_id)
if not meta_calls_allowed(account):
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Reconnect this WhatsApp account before creating a template.")
connection = account.connection
token = connection.access_token if connection else None
if connection is None or not token or connection.status == "disconnected":
@ -1187,7 +1200,7 @@ def _messaging_phone(account: WhatsAppBusinessAccount) -> tuple[WhatsAppPhoneAss
connection = account.connection
token = connection.access_token if connection else None
phone = next((item for item in account.phones if item.status == "connected"), None)
if phone is None or account.messaging_status != "active" or not token:
if not meta_calls_allowed(account) or phone is None or account.messaging_status != "active" or not token:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=account.last_error or "Messaging is unavailable until this number is connected on Cloud API",
@ -1198,6 +1211,8 @@ def _messaging_phone(account: WhatsAppBusinessAccount) -> tuple[WhatsAppPhoneAss
async def enable_customer_delivery(db: Session, user: UserAccount, account_id: Any, app_secret: str) -> dict[str, Any]:
"""Point this account's replies and delivery updates at the workspace."""
account = _owned_account(db, user, account_id)
if not meta_calls_allowed(account):
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Reconnect this WhatsApp account before replies can arrive here.")
connection = account.connection
token = connection.access_token if connection else None
if connection is None or not token or connection.status == "disconnected":
@ -1383,6 +1398,8 @@ async def send_account_broadcast(
async def download_account_media(db: Session, user: UserAccount, account_id: Any, media_id: str) -> tuple[bytes, str]:
account = _owned_account(db, user, account_id)
if not meta_calls_allowed(account):
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Reconnect this WhatsApp account before opening media")
token = account.connection.access_token if account.connection else None
if not token:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Reconnect this WhatsApp account before opening media")

View file

@ -20,6 +20,7 @@ from .whatsapp_onboarding import (
_load_phones,
_load_waba,
_owned_account,
meta_calls_allowed,
_quality_label,
_token_app_id,
_waba_ids_from_debug,
@ -613,6 +614,8 @@ async def _correct_waba_id(db: Session, account: WhatsAppBusinessAccount, token:
async def sync_account_templates(db: Session, account: WhatsAppBusinessAccount) -> str | None:
if not meta_calls_allowed(account):
return None
tokens = _candidate_tokens(db, account)
if not tokens:
if account.status == "disconnected":
@ -671,6 +674,7 @@ async def list_managed_templates(db: Session, user: UserAccount, account_id: str
accounts = db.scalars(query).all()
if account_id:
accounts = [account for account in accounts if str(account.id) == account_id]
accounts = [account for account in accounts if meta_calls_allowed(account)]
warnings = []
for account in accounts:
warning = await sync_account_templates(db, account)
@ -746,8 +750,14 @@ def _validate_identity(payload: dict[str, Any]) -> tuple[str, str, str]:
return name, language, category
def _require_connected(account: WhatsAppBusinessAccount) -> None:
if not meta_calls_allowed(account):
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Reconnect this WhatsApp account before managing templates.")
async def create_managed_template(db: Session, user: UserAccount, payload: dict[str, Any]) -> dict[str, Any]:
account = _owned_account(db, user, payload.get("account_id"))
_require_connected(account)
token = _token(db, account)
name, language, category = _validate_identity(payload)
existing = db.scalar(
@ -793,6 +803,7 @@ async def create_managed_template(db: Session, user: UserAccount, payload: dict[
async def resubmit_managed_template(db: Session, user: UserAccount, template_id: Any, payload: dict[str, Any]) -> dict[str, Any]:
row, account = _owned_template(db, user, template_id)
_require_connected(account)
if row.status not in {"REJECTED", "PAUSED", "APPROVED"}:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="This template can be edited after Meta rejects it, pauses it, or approves it.")
if not row.meta_template_id:
@ -812,6 +823,7 @@ async def resubmit_managed_template(db: Session, user: UserAccount, template_id:
async def delete_managed_template(db: Session, user: UserAccount, template_id: Any) -> dict[str, str]:
row, account = _owned_template(db, user, template_id)
_require_connected(account)
params: dict[str, Any] = {"name": row.name}
if row.meta_template_id:
params["hsm_id"] = row.meta_template_id
@ -823,6 +835,7 @@ async def delete_managed_template(db: Session, user: UserAccount, template_id: A
async def upload_header_media(db: Session, user: UserAccount, account_id: Any, filename: str, content_type: str, data: bytes) -> dict[str, str]:
account = _owned_account(db, user, account_id)
_require_connected(account)
token = _token(db, account)
kind = _MEDIA.get(content_type)
if kind is None: