feat: async PDF cache i API media putanje za putne naloge
Uvodi background request tok za generiranje PDF-a s 24h cache-om i cleanup taskom preko Celery beat-a. Dodaje notification metadata download_url za ponovno preuzimanje bez regeneracije, API image endpointe za putne naloge i račune, te frontend integraciju za async download i produkcijski prikaz slika. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -3,6 +3,7 @@ from celery import shared_task
|
||||
from django.core.mail import send_mail
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from django.core.files.base import ContentFile
|
||||
import logging
|
||||
from io import BytesIO
|
||||
import base64
|
||||
@@ -10,7 +11,7 @@ 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 .models import VehicleNotification, GeneratedWorkOrderPdf
|
||||
from .pdf_layout import register_unicode_fonts, draw_standard_header_footer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -205,4 +206,106 @@ def build_work_order_invoices_pdf_task(work_order_id):
|
||||
return {
|
||||
"filename": filename,
|
||||
"pdf_base64": pdf_b64,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@shared_task
|
||||
def cleanup_expired_generated_pdfs_task():
|
||||
now = timezone.now()
|
||||
expired = GeneratedWorkOrderPdf.objects.filter(
|
||||
is_active=True,
|
||||
expires_at__isnull=False,
|
||||
expires_at__lte=now,
|
||||
)
|
||||
deleted = 0
|
||||
for item in expired:
|
||||
if item.file:
|
||||
item.file.delete(save=False)
|
||||
item.is_active = False
|
||||
item.status = 'failed'
|
||||
item.error_message = 'PDF cache istekao.'
|
||||
item.save(update_fields=['is_active', 'status', 'error_message', 'updated_at'])
|
||||
deleted += 1
|
||||
return {"deleted": deleted}
|
||||
|
||||
|
||||
@shared_task
|
||||
def build_work_order_pdf_cached_task(generated_pdf_id):
|
||||
from .views import _build_work_order_pdf, _build_work_order_service_records_pdf
|
||||
from .services import NotificationService
|
||||
|
||||
generated = (
|
||||
GeneratedWorkOrderPdf.objects
|
||||
.select_related('work_order', 'work_order__vehicle', 'requested_by')
|
||||
.filter(pk=generated_pdf_id, is_active=True)
|
||||
.first()
|
||||
)
|
||||
if generated is None:
|
||||
return {"error": "Generated PDF zapis nije pronađen."}
|
||||
|
||||
work_order = generated.work_order
|
||||
|
||||
try:
|
||||
if generated.pdf_type == 'invoices':
|
||||
payload = build_work_order_invoices_pdf_task(str(work_order.pk))
|
||||
if not isinstance(payload, dict) or payload.get('error'):
|
||||
raise ValueError(payload.get('error') if isinstance(payload, dict) else 'Neuspješno generiranje PDF računa.')
|
||||
pdf_b64 = payload.get('pdf_base64')
|
||||
if not pdf_b64:
|
||||
raise ValueError('PDF sadržaj nije dostupan.')
|
||||
pdf_bytes = base64.b64decode(pdf_b64)
|
||||
elif generated.pdf_type == 'service_records':
|
||||
pdf_bytes = _build_work_order_service_records_pdf(work_order)
|
||||
else:
|
||||
pdf_bytes = _build_work_order_pdf(work_order)
|
||||
|
||||
filename = generated.filename or f"{work_order.pk}.{generated.pdf_type}.pdf"
|
||||
generated.file.save(filename, ContentFile(pdf_bytes), save=False)
|
||||
generated.status = 'ready'
|
||||
generated.error_message = ''
|
||||
generated.save(update_fields=['file', 'status', 'error_message', 'updated_at'])
|
||||
|
||||
if generated.requested_by:
|
||||
NotificationService.create_notification(
|
||||
recipient=generated.requested_by,
|
||||
title="PDF spreman",
|
||||
message=f"PDF dokument je uspješno generiran za WO-{work_order.pk}.",
|
||||
level="success",
|
||||
send_email=False,
|
||||
metadata={
|
||||
"entity_type": "work_order_pdf",
|
||||
"work_order_id": str(work_order.pk),
|
||||
"vehicle_id": str(work_order.vehicle_id),
|
||||
"section": "work-orders",
|
||||
"pdf_type": generated.pdf_type,
|
||||
"stage": "completed",
|
||||
"generated_pdf_id": str(generated.pk),
|
||||
"download_url": f"/api/fleet/work-orders/{work_order.pk}/generated-pdfs/{generated.pk}/download/",
|
||||
"filename": generated.filename or filename,
|
||||
"expires_at": generated.expires_at.isoformat() if generated.expires_at else None,
|
||||
},
|
||||
)
|
||||
return {"status": "ready", "generated_pdf_id": str(generated.pk)}
|
||||
except Exception as exc:
|
||||
generated.status = 'failed'
|
||||
generated.error_message = str(exc)
|
||||
generated.save(update_fields=['status', 'error_message', 'updated_at'])
|
||||
if generated.requested_by:
|
||||
NotificationService.create_notification(
|
||||
recipient=generated.requested_by,
|
||||
title="Greška kod PDF-a",
|
||||
message=f"Generiranje PDF dokumenta nije uspjelo za WO-{work_order.pk}.",
|
||||
level="warning",
|
||||
send_email=False,
|
||||
metadata={
|
||||
"entity_type": "work_order_pdf",
|
||||
"work_order_id": str(work_order.pk),
|
||||
"vehicle_id": str(work_order.vehicle_id),
|
||||
"section": "work-orders",
|
||||
"pdf_type": generated.pdf_type,
|
||||
"stage": "failed",
|
||||
"generated_pdf_id": str(generated.pk),
|
||||
},
|
||||
)
|
||||
logger.exception("Greška kod build_work_order_pdf_cached_task: %s", exc)
|
||||
return {"status": "failed", "error": str(exc)}
|
||||
Reference in New Issue
Block a user