Formate zentral aus Directus + Kuvert-/Agent-/Login-Fixes

Formate zentralisiert (Phase 1)
- Neue Collection render_formats (Seitengröße, Ränder, Zeilenhöhe/-limit +
  Plotter-Template) als einzige Quelle. Backend liest sie gecacht (Start +
  stündlich, synchroner Zugriff → keine Latenz) mit eingebauten Defaults als
  Fallback; manueller Trigger via POST /api/formats/refresh.
- Ermöglicht frei definierbare Formate ohne Codeänderung.

Kuvert-Templates
- Werte 1:1 aus dem alten Job-Manager übernommen (falsche/vertauschte Maße
  korrigiert, per Upsert) und je Format die zweite Variante ergänzt
  (individuelle Beschriftung). Druck-Popup schlägt bei freiem Text Variante 2 vor.

Produktions-Agent
- Datei-Endpunkt liefert {files} ohne data-Hülle → eigener get_raw statt des
  data-auspackenden get; Dokumentauswahl positionsbasiert (robust gegen 0-/1-
  basierte Dateinamen).

Bootstrap
- Ungültiger DIRECTUS_TOKEN wird erkannt und fällt auf ADMIN_EMAIL/PASSWORD
  zurück, statt mit einem 401 abzubrechen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Lucas Orth
2026-07-24 09:05:09 +02:00
parent 445ebdec5a
commit f77ddb6a4c
7 changed files with 181 additions and 76 deletions

View File

@@ -46,10 +46,17 @@ class Directus:
self.s.headers["Authorization"] = f"Bearer {token}"
def get(self, path, **params):
"""Für /items/… – Directus verpackt die Nutzdaten in `data`."""
r = self.s.get(self.base + path, params=params, timeout=30)
r.raise_for_status()
return r.json().get("data")
def get_raw(self, path, **params):
"""Für Custom-Endpunkte (z. B. /skrift-orders/files) ohne `data`-Hülle."""
r = self.s.get(self.base + path, params=params, timeout=30)
r.raise_for_status()
return r.json()
def patch(self, path, body):
r = self.s.patch(self.base + path, json=body, timeout=30)
r.raise_for_status()
@@ -152,12 +159,6 @@ def parse_nummern(spec):
return treffer or None
def datei_nummer(name):
"""Zahl aus einem Dateinamen ziehen (letter_003.svg → 3)."""
ziffern = "".join(ch for ch in os.path.splitext(os.path.basename(name))[0] if ch.isdigit())
return int(ziffern) if ziffern else None
def main():
cfg = lade_config()
dx = Directus(cfg["directus_url"], cfg["directus_token"])
@@ -248,15 +249,18 @@ def _druck(dx, plotter, job, KIND):
try:
if not order_nr:
raise RuntimeError("Auftragsnummer fehlt am Job.")
liste = dx.get(f"/skrift-orders/files/{order_nr}") or {}
liste = dx.get_raw(f"/skrift-orders/files/{order_nr}") or {}
gewuenscht = KIND.get(typ, "schriftstueck")
passende = sorted(
[f for f in (liste.get("files") or []) if f.get("kind") == gewuenscht],
key=lambda f: f.get("name", ""),
)
# Nur bestimmte Briefnummern drucken, falls im Job angegeben.
# Positionsbasiert (1 = erstes Dokument), NICHT über die Zahl im
# Dateinamen: Alt-Aufträge sind 0-basiert (letter_000.svg), neue
# 1-basiert (letter_001.svg). Die Position stimmt in beiden Fällen.
if nummern is not None:
passende = [f for f in passende if datei_nummer(f.get("name")) in nummern]
passende = [f for i, f in enumerate(passende, start=1) if i in nummern]
if not passende:
raise RuntimeError(f"Keine Dateien vom Typ '{gewuenscht}' für {order_nr} gefunden.")