Files
004.erp.generic/backend/modules/fleet/tasks.py
mariomitte 4e3bf6dab0
Some checks failed
ERP CI/CD Pipeline / test (push) Has been cancelled
ERP CI/CD Pipeline / Deploy (server git pull + compose) (push) Has been cancelled
patch oko teksta PDFa i UI tablica
2026-07-12 03:25:03 +02:00

208 lines
7.5 KiB
Python

# backend/modules/fleet/tasks.py
from celery import shared_task
from django.core.mail import send_mail
from django.conf import settings
from django.utils import timezone
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
from .pdf_layout import register_unicode_fonts, draw_standard_header_footer
logger = logging.getLogger(__name__)
register_unicode_fonts()
@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
margin = 28
header_h = 86
footer_h = 72
content_top = height - header_h - 10
content_bottom = footer_h + 52
client_name = getattr(getattr(work_order.vehicle, "client", None), "name", None) or "-"
manufacturer = str(work_order.vehicle.make or "-")
model_line = str(work_order.vehicle.model or "-")
serial_line = str(getattr(work_order.vehicle, "crane_serial_number", None) or "-")
upgrade_hours = str(getattr(work_order.vehicle, "superstructure_working_hours", "-") or "-")
chassis_hours = str(getattr(work_order.vehicle, "chassis_working_hours", "-") or "-")
mileage = str(getattr(work_order.vehicle, "current_mileage", "-") or "-")
generated_date = timezone.localtime(timezone.now()).strftime("%d.%m.%Y")
def draw_header_footer(page_num):
draw_standard_header_footer(
pdf,
page_num=page_num,
client_name=client_name,
manufacturer=manufacturer,
model=model_line,
serial=serial_line,
upgrade_hours=upgrade_hours,
chassis_hours=chassis_hours,
mileage=mileage,
work_order_number=str(work_order.pk),
generated_date=generated_date,
report_title="Računi putnog naloga",
page_size=A4,
margin=margin,
header_h=header_h,
footer_h=footer_h,
)
page_num = 1
draw_header_footer(page_num)
y = content_top
y -= 8
pdf.setFont("Vera-Bold", 13)
pdf.drawString(margin, y, f"Računi putnog naloga WO-{work_order.pk}")
y -= 20
pdf.setFont("Vera", 10)
pdf.drawString(margin, 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 -= 20
pdf.drawString(margin, y, "Nema računa za ovaj putni nalog.")
pdf.save()
return buffer.getvalue()
for index, invoice in enumerate(invoices, start=1):
pdf.showPage()
page_num += 1
draw_header_footer(page_num)
y = content_top
y -= 8
pdf.setFont("Vera-Bold", 13)
pdf.drawString(margin, y, f"Račun #{index}")
y -= 22
pdf.setFont("Vera", 10)
pdf.drawString(margin, y, f"naziv_racuna: {invoice.naziv_racuna or '-'}")
y -= 16
pdf.drawString(margin, y, f"lokacija: {invoice.lokacija or '-'}")
y -= 16
pdf.drawString(margin, y, f"datum: {invoice.datum.strftime('%d.%m.%Y') if invoice.datum else '-'}")
y -= 16
pdf.drawString(margin, y, f"opis: {(invoice.opis or '-')[:140]}")
y -= 20
if not invoice.image:
pdf.drawString(margin, y, "slika: Nema slike.")
continue
try:
invoice.image.open('rb')
with Image.open(invoice.image) as source:
image = source.convert('RGB')
max_width = width - (2 * margin)
max_height = y - content_bottom
if max_height < 120:
pdf.showPage()
page_num += 1
draw_header_footer(page_num)
y = content_top
max_height = y - content_bottom
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,
margin,
y - draw_height,
width=draw_width,
height=draw_height,
preserveAspectRatio=True,
mask='auto',
)
except (UnidentifiedImageError, OSError):
pdf.drawString(margin, 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,
}