Spaces:
Sleeping
Sleeping
File size: 4,908 Bytes
db4ba8d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | """
TradeFlow AI — Notification Service (T-074)
Sends notifications via:
1. Resend (email)
2. WhatsApp Business API (optional, behind feature flag)
Called on:
- REVIEW_READY → operator email
- ACCEPTED / REJECTED → importer email + WhatsApp (if enabled)
"""
from __future__ import annotations
import logging
from typing import Any
import httpx
logger = logging.getLogger("services.notification")
RESEND_API_URL = "https://api.resend.com/emails"
class NotificationService:
def __init__(self, settings: Any) -> None:
self._settings = settings
async def notify_review_ready(
self,
batch_id: str,
operator_email: str,
crs_score: int,
risk_level: str,
) -> None:
"""Notify operator that a batch is ready for review."""
if not self._settings.RESEND_API_KEY:
logger.info("RESEND_API_KEY not set — skipping email notification")
return
subject = f"[TradeFlow AI] Batch {batch_id[:8]}… ready for review"
html = f"""
<h2>Batch Ready for Review</h2>
<p>A new import declaration batch is ready for your review.</p>
<table>
<tr><td><b>Batch ID</b></td><td>{batch_id}</td></tr>
<tr><td><b>CRS Score</b></td><td>{crs_score}/100</td></tr>
<tr><td><b>Risk Level</b></td><td>{risk_level}</td></tr>
</table>
<p><a href="https://app.tradeflow.ai/batches/{batch_id}">Review Now →</a></p>
"""
await self._send_email(to=operator_email, subject=subject, html=html)
async def notify_ceisa_result(
self,
batch_id: str,
ceisa_status: str,
aju_number: str,
recipient_email: str,
recipient_phone: str | None = None,
) -> None:
"""Notify importer of CEISA acceptance or rejection."""
accepted = ceisa_status == "ACCEPTED"
subject = (
f"[TradeFlow AI] PIB {'Diterima' if accepted else 'Ditolak'} — AJU {aju_number}"
)
html = f"""
<h2>{"✅ PIB Diterima" if accepted else "❌ PIB Ditolak"}</h2>
<p>Status PIB Anda telah diperbarui:</p>
<table>
<tr><td><b>Batch ID</b></td><td>{batch_id}</td></tr>
<tr><td><b>Nomor AJU</b></td><td>{aju_number}</td></tr>
<tr><td><b>Status</b></td><td>{ceisa_status}</td></tr>
</table>
<p><a href="https://app.tradeflow.ai/batches/{batch_id}">Lihat Detail →</a></p>
"""
await self._send_email(to=recipient_email, subject=subject, html=html)
# WhatsApp (optional)
if (
self._settings.ENABLE_NOTIFICATIONS_WHATSAPP
and recipient_phone
and self._settings.WHATSAPP_TOKEN
):
msg = (
f"TradeFlow AI: PIB Anda {'*DITERIMA*' if accepted else '*DITOLAK*'} "
f"oleh CEISA. Nomor AJU: {aju_number}. "
f"Detail: https://app.tradeflow.ai/batches/{batch_id}"
)
await self._send_whatsapp(phone=recipient_phone, message=msg)
async def _send_email(self, to: str, subject: str, html: str) -> None:
try:
async with httpx.AsyncClient(timeout=10.0) as client:
resp = await client.post(
RESEND_API_URL,
headers={
"Authorization": f"Bearer {self._settings.RESEND_API_KEY.get_secret_value()}",
"Content-Type": "application/json",
},
json={
"from": self._settings.NOTIFICATION_EMAIL_FROM,
"to": [to],
"subject": subject,
"html": html,
},
)
resp.raise_for_status()
logger.info(f"Email sent to {to}: {subject}")
except Exception as e:
logger.error(f"Email send failed: {e}")
async def _send_whatsapp(self, phone: str, message: str) -> None:
try:
phone_id = self._settings.WHATSAPP_PHONE_NUMBER_ID
token = self._settings.WHATSAPP_TOKEN.get_secret_value()
async with httpx.AsyncClient(timeout=10.0) as client:
resp = await client.post(
f"https://graph.facebook.com/v20.0/{phone_id}/messages",
headers={"Authorization": f"Bearer {token}"},
json={
"messaging_product": "whatsapp",
"to": phone.replace("+", ""),
"type": "text",
"text": {"body": message},
},
)
resp.raise_for_status()
logger.info(f"WhatsApp sent to {phone}")
except Exception as e:
logger.error(f"WhatsApp send failed: {e}")
|