Erste lauffähige Version
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
"""
|
||||
Celery-Tasks für die PDF-Erzeugung.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import io
|
||||
import logging
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from celery import shared_task
|
||||
from django.core.files import File
|
||||
from django.utils import timezone
|
||||
|
||||
from .models import JobLogEntry, MailMergeJob
|
||||
from .services.docx_renderer import docx_to_pdf, render_docx
|
||||
from .services.pdf_merge import merge_pdfs
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _log(job: MailMergeJob, level: str, msg: str) -> None:
|
||||
JobLogEntry.objects.create(job=job, level=level, message=msg)
|
||||
logger.log(getattr(logging, level.upper(), logging.INFO), "[job %s] %s", job.id, msg)
|
||||
|
||||
|
||||
@shared_task(bind=True)
|
||||
def run_mailmerge(self, job_id: str) -> str:
|
||||
job = MailMergeJob.objects.select_related("template").get(pk=job_id)
|
||||
job.status = MailMergeJob.Status.RUNNING
|
||||
job.started_at = timezone.now()
|
||||
job.save(update_fields=["status", "started_at"])
|
||||
_log(job, "info", f"Job gestartet (task={self.request.id}).")
|
||||
|
||||
try:
|
||||
# CSV einlesen
|
||||
raw = job.recipients_csv.read().decode("utf-8-sig")
|
||||
reader = csv.DictReader(io.StringIO(raw))
|
||||
rows = list(reader)
|
||||
job.total_rows = len(rows)
|
||||
job.save(update_fields=["total_rows"])
|
||||
_log(job, "info", f"{len(rows)} Empfänger gefunden.")
|
||||
|
||||
if not rows:
|
||||
raise ValueError("CSV enthält keine Datenzeilen.")
|
||||
|
||||
# CSV-Felder vs. Template-Platzhalter prüfen
|
||||
csv_fields = set(reader.fieldnames or [])
|
||||
placeholders = set(job.template.placeholders or [])
|
||||
missing = placeholders - csv_fields
|
||||
if missing:
|
||||
_log(job, "warning",
|
||||
f"CSV fehlen Spalten: {', '.join(sorted(missing))}")
|
||||
|
||||
# Jeden Brief rendern, alle zu einem PDF zusammenführen
|
||||
with tempfile.TemporaryDirectory(prefix="mailmerge-") as tmpdir:
|
||||
tmp = Path(tmpdir)
|
||||
pdfs: list[Path] = []
|
||||
template_path = Path(job.template.file.path)
|
||||
|
||||
for idx, row in enumerate(rows, start=1):
|
||||
docx_out = tmp / f"letter_{idx:05d}.docx"
|
||||
render_docx(template_path, row, docx_out)
|
||||
pdf = docx_to_pdf(docx_out, tmp / "pdf")
|
||||
pdfs.append(pdf)
|
||||
job.processed_rows = idx
|
||||
job.save(update_fields=["processed_rows"])
|
||||
|
||||
merged = tmp / f"serienbrief_{job.id}.pdf"
|
||||
merge_pdfs(pdfs, merged)
|
||||
with merged.open("rb") as f:
|
||||
job.result_pdf.save(merged.name, File(f), save=False)
|
||||
|
||||
job.status = MailMergeJob.Status.DONE
|
||||
job.finished_at = timezone.now()
|
||||
job.save()
|
||||
_log(job, "info", "Job erfolgreich abgeschlossen.")
|
||||
return str(job.id)
|
||||
|
||||
except Exception as exc: # noqa: BLE001
|
||||
job.status = MailMergeJob.Status.FAILED
|
||||
job.error_message = str(exc)
|
||||
job.finished_at = timezone.now()
|
||||
job.save()
|
||||
_log(job, "error", f"Job fehlgeschlagen: {exc}")
|
||||
raise
|
||||
Reference in New Issue
Block a user