This commit is contained in:
165
backend/modules/fleet/tasks.py
Normal file
165
backend/modules/fleet/tasks.py
Normal file
@@ -0,0 +1,165 @@
|
||||
# backend/modules/fleet/tasks.py
|
||||
from celery import shared_task
|
||||
from django.core.mail import send_mail
|
||||
from django.conf import settings
|
||||
import logging
|
||||
from io import BytesIO
|
||||
import base64
|
||||
from PIL import Image, UnidentifiedImageError
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from reportlab.lib.utils import ImageReader
|
||||
from reportlab.pdfgen import canvas
|
||||
from .models import VehicleNotification
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@shared_task(bind=True, max_retries=3, default_retry_delay=60)
|
||||
def send_notification_email(self, notification_id):
|
||||
"""
|
||||
Celery task koji pošalje e-mail za VehicleNotification i označi is_sent = True.
|
||||
Prihvaća notification_id (PK) i radi safe-lookup.
|
||||
U slučaju greške rekreira retry.
|
||||
"""
|
||||
try:
|
||||
# Lokalni import kako bismo izbjegli kružne importove
|
||||
from .models import VehicleNotification
|
||||
notif = VehicleNotification.objects.select_related('recipient').get(pk=notification_id)
|
||||
except Exception as exc:
|
||||
logger.warning(f"Notification {notification_id} ne postoji ili nije dostupna: {exc}")
|
||||
return
|
||||
|
||||
recipient = notif.recipient
|
||||
recipient_email = getattr(recipient, 'email', None) if recipient else None
|
||||
|
||||
if not recipient_email:
|
||||
logger.info(f"Notification {notification_id} nema email primatelja; preskačem slanje.")
|
||||
return
|
||||
|
||||
subject = notif.title
|
||||
message = notif.message
|
||||
from_email = getattr(settings, "DEFAULT_FROM_EMAIL", None)
|
||||
|
||||
try:
|
||||
send_mail(
|
||||
subject=subject,
|
||||
message=message,
|
||||
from_email=from_email,
|
||||
recipient_list=[recipient_email],
|
||||
fail_silently=False,
|
||||
)
|
||||
notif.is_sent = True
|
||||
notif.save(update_fields=['is_sent'])
|
||||
logger.info(f"Notification email poslan za Notification(id={notification_id}) na {recipient_email}")
|
||||
except Exception as exc:
|
||||
logger.exception(f"Greška pri slanju emaila za Notification(id={notification_id}): {exc}")
|
||||
try:
|
||||
# Retry with exponential backoff handled by celery config; here max_retries = 3
|
||||
raise self.retry(exc=exc)
|
||||
except self.MaxRetriesExceededError:
|
||||
logger.error(f"Max retries exceeded za Notification(id={notification_id})")
|
||||
|
||||
@shared_task
|
||||
def send_email_task(recipient_email, subject, message, notif_id):
|
||||
try:
|
||||
send_mail(
|
||||
subject=subject,
|
||||
message=message,
|
||||
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
recipient_list=[recipient_email],
|
||||
fail_silently=False,
|
||||
)
|
||||
VehicleNotification.objects.filter(id=notif_id).update(is_sent=True)
|
||||
except Exception as exc:
|
||||
logger.error(f"Async mail failed: {exc}")
|
||||
|
||||
|
||||
def _build_work_order_invoices_pdf(work_order):
|
||||
buffer = BytesIO()
|
||||
pdf = canvas.Canvas(buffer, pagesize=A4)
|
||||
width, height = A4
|
||||
y = height - 50
|
||||
|
||||
pdf.setFont("Helvetica-Bold", 14)
|
||||
pdf.drawString(40, y, f"Računi putnog naloga WO-{work_order.pk}")
|
||||
y -= 22
|
||||
pdf.setFont("Helvetica", 10)
|
||||
pdf.drawString(40, y, f"Dizalica: {work_order.vehicle.registration_number}")
|
||||
y -= 16
|
||||
pdf.drawString(40, y, f"Datum naloga: {work_order.date}")
|
||||
|
||||
invoices = work_order.invoices.filter(is_active=True).order_by('-datum', '-created_at')
|
||||
if not invoices.exists():
|
||||
y -= 24
|
||||
pdf.drawString(40, y, "Nema računa za ovaj putni nalog.")
|
||||
pdf.save()
|
||||
return buffer.getvalue()
|
||||
|
||||
for index, invoice in enumerate(invoices, start=1):
|
||||
pdf.showPage()
|
||||
y = height - 50
|
||||
pdf.setFont("Helvetica-Bold", 13)
|
||||
pdf.drawString(40, y, f"Račun #{index}")
|
||||
y -= 22
|
||||
pdf.setFont("Helvetica", 10)
|
||||
pdf.drawString(40, y, f"naziv_racuna: {invoice.naziv_racuna or '-'}")
|
||||
y -= 16
|
||||
pdf.drawString(40, y, f"lokacija: {invoice.lokacija or '-'}")
|
||||
y -= 16
|
||||
pdf.drawString(40, y, f"datum: {invoice.datum.strftime('%d.%m.%Y') if invoice.datum else '-'}")
|
||||
y -= 16
|
||||
pdf.drawString(40, y, f"opis: {(invoice.opis or '-')[:140]}")
|
||||
y -= 20
|
||||
|
||||
if not invoice.image:
|
||||
pdf.drawString(40, y, "slika: Nema slike.")
|
||||
continue
|
||||
|
||||
try:
|
||||
invoice.image.open('rb')
|
||||
with Image.open(invoice.image) as source:
|
||||
image = source.convert('RGB')
|
||||
max_width = width - 80
|
||||
max_height = y - 60
|
||||
if max_height < 120:
|
||||
pdf.showPage()
|
||||
y = height - 60
|
||||
max_height = y - 60
|
||||
|
||||
ratio = min(max_width / float(image.width), max_height / float(image.height), 1.0)
|
||||
draw_width = max(1, int(image.width * ratio))
|
||||
draw_height = max(1, int(image.height * ratio))
|
||||
image_reader = ImageReader(image)
|
||||
pdf.drawImage(
|
||||
image_reader,
|
||||
40,
|
||||
y - draw_height,
|
||||
width=draw_width,
|
||||
height=draw_height,
|
||||
preserveAspectRatio=True,
|
||||
mask='auto',
|
||||
)
|
||||
except (UnidentifiedImageError, OSError):
|
||||
pdf.drawString(40, y, "slika: Slika nije dostupna ili je oštećena.")
|
||||
finally:
|
||||
invoice.image.close()
|
||||
|
||||
pdf.save()
|
||||
return buffer.getvalue()
|
||||
|
||||
|
||||
@shared_task
|
||||
def build_work_order_invoices_pdf_task(work_order_id):
|
||||
from .models import WorkOrder
|
||||
|
||||
work_order = WorkOrder.objects.select_related('vehicle').filter(pk=work_order_id).first()
|
||||
if work_order is None:
|
||||
return {"error": "Putni nalog nije pronađen."}
|
||||
|
||||
pdf_bytes = _build_work_order_invoices_pdf(work_order)
|
||||
pdf_b64 = base64.b64encode(pdf_bytes).decode('ascii')
|
||||
filename = f"{work_order.pk}.work-order-invoices.pdf"
|
||||
return {
|
||||
"filename": filename,
|
||||
"pdf_base64": pdf_b64,
|
||||
}
|
||||
Reference in New Issue
Block a user