feat: Implement asynchronous invoice report generation with email delivery and frontend status polling.
This commit is contained in:
@@ -12,7 +12,8 @@ celery_app = Celery(
|
||||
"api.v1.modules.a76.reports.importacion.facturas.task",
|
||||
"api.v1.modules.a76.reports.importacion.consolidados.task",
|
||||
"api.v1.modules.a76.reports.importacion.packing_list.task",
|
||||
"api.v1.modules.a76.reports.exportacion.aviso_consolidado.task"
|
||||
"api.v1.modules.a76.reports.exportacion.aviso_consolidado.task",
|
||||
"api.v1.modules.a76.reports.movements.invoices.tasks"
|
||||
] # Ruta al módulo donde están las tareas
|
||||
)
|
||||
|
||||
|
||||
@@ -47,6 +47,14 @@ class Settings(BaseSettings):
|
||||
SITAR_API_USER: str = ""
|
||||
SITAR_API_PASSWORD: str = ""
|
||||
|
||||
# SMTP Email Configuration
|
||||
SMTP_HOST: str = "smtp.gmail.com"
|
||||
SMTP_PORT: int = 587
|
||||
SMTP_USER: str = ""
|
||||
SMTP_PASSWORD: str = ""
|
||||
SMTP_FROM_NAME: str = "Sistema Anexo76"
|
||||
SMTP_USE_TLS: bool = True
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
env_file=[".env", "../.env"],
|
||||
case_sensitive=True,
|
||||
|
||||
117
backend/core/email.py
Normal file
117
backend/core/email.py
Normal file
@@ -0,0 +1,117 @@
|
||||
"""
|
||||
Email service for sending reports via SMTP.
|
||||
"""
|
||||
import aiosmtplib
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.base import MIMEBase
|
||||
from email import encoders
|
||||
from typing import List
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from core.config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EmailService:
|
||||
"""Service for sending emails with attachments."""
|
||||
|
||||
@staticmethod
|
||||
async def send_report_email(
|
||||
recipient_email: str,
|
||||
subject: str,
|
||||
body_text: str,
|
||||
csv_content: str,
|
||||
filename: str
|
||||
) -> bool:
|
||||
"""
|
||||
Send a report email with CSV attachment.
|
||||
|
||||
Args:
|
||||
recipient_email: Email address of recipient
|
||||
subject: Email subject line
|
||||
body_text: Plain text email body
|
||||
csv_content: CSV file content as string
|
||||
filename: Name for the CSV attachment
|
||||
|
||||
Returns:
|
||||
bool: True if email sent successfully, False otherwise
|
||||
"""
|
||||
try:
|
||||
# Create message
|
||||
msg = MIMEMultipart()
|
||||
msg['From'] = f"{settings.SMTP_FROM_NAME} <{settings.SMTP_USER}>"
|
||||
msg['To'] = recipient_email
|
||||
msg['Subject'] = subject
|
||||
|
||||
# Email body
|
||||
html_body = f"""
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
|
||||
<h2 style="color: #2563eb; border-bottom: 2px solid #2563eb; padding-bottom: 10px;">
|
||||
Reporte de Facturas - Sistema Anexo76
|
||||
</h2>
|
||||
<p>{body_text}</p>
|
||||
<p style="margin-top: 20px;">
|
||||
El reporte se encuentra adjunto en formato CSV.
|
||||
</p>
|
||||
<hr style="margin: 30px 0; border: none; border-top: 1px solid #e5e7eb;">
|
||||
<p style="font-size: 12px; color: #6b7280;">
|
||||
Este es un correo generado automáticamente. Por favor no responder.
|
||||
</p>
|
||||
<p style="font-size: 12px; color: #6b7280;">
|
||||
Generado el {datetime.now().strftime('%d/%m/%Y a las %H:%M')}
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
msg.attach(MIMEText(html_body, 'html'))
|
||||
|
||||
# CSV attachment
|
||||
attachment = MIMEBase('text', 'csv')
|
||||
attachment.set_payload(csv_content.encode('utf-8'))
|
||||
encoders.encode_base64(attachment)
|
||||
attachment.add_header(
|
||||
'Content-Disposition',
|
||||
f'attachment; filename="{filename}"'
|
||||
)
|
||||
msg.attach(attachment)
|
||||
|
||||
# Create SSL context that ignores certificate errors
|
||||
import ssl
|
||||
context = ssl.create_default_context()
|
||||
context.check_hostname = False
|
||||
context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
# Send email
|
||||
if settings.SMTP_PORT == 465:
|
||||
# Port 465 uses implicit SSL
|
||||
async with aiosmtplib.SMTP(
|
||||
hostname=settings.SMTP_HOST,
|
||||
port=settings.SMTP_PORT,
|
||||
use_tls=True, # Implicit SSL
|
||||
tls_context=context
|
||||
) as smtp:
|
||||
await smtp.login(settings.SMTP_USER, settings.SMTP_PASSWORD)
|
||||
await smtp.send_message(msg)
|
||||
else:
|
||||
# Port 587 uses STARTTLS
|
||||
async with aiosmtplib.SMTP(
|
||||
hostname=settings.SMTP_HOST,
|
||||
port=settings.SMTP_PORT,
|
||||
tls_context=context
|
||||
) as smtp:
|
||||
await smtp.starttls(tls_context=context)
|
||||
await smtp.login(settings.SMTP_USER, settings.SMTP_PASSWORD)
|
||||
await smtp.send_message(msg)
|
||||
|
||||
logger.info(f"Email sent successfully to {recipient_email}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send email to {recipient_email}: {str(e)}")
|
||||
return False
|
||||
Reference in New Issue
Block a user