fix: workflow 422 validation + report 500 DB data fetching + AI stream tenant context
Check Cross-Plugin Imports / check (push) Has been cancelled

This commit is contained in:
Agent Zero
2026-07-30 10:54:28 +02:00
parent 3f2f594847
commit 2cd3f30f82
2 changed files with 110 additions and 7 deletions
@@ -15,7 +15,10 @@ from fastapi.responses import StreamingResponse
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.db import get_db
from app.models.contact import Contact
from app.models.audit import AuditLog
from app.core.db import get_db, set_tenant_context
from app.deps import get_current_user, require_permission
from app.plugins.builtins.report_generator.models import (
ReportInstance,
@@ -156,15 +159,100 @@ async def generate_preset(
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(require_permission("reports:generate")),
):
"""Generate a preset report by name (e.g. contact_list, calendar_week)."""
"""Generate a preset report by name (e.g. contact_list, calendar_week).
Fetches live data from the database based on the preset type,
merges with user-provided parameters, and generates the report.
"""
tenant_id = uuid.UUID(current_user["tenant_id"])
user_id = uuid.UUID(current_user["user_id"])
# Generate report first (no DB interaction during sync IO)
await db.close()
# Set tenant context for RLS
await set_tenant_context(db, tenant_id)
# Fetch data based on preset type
parameters = dict(body.parameters) # copy user params
try:
if body.preset == "contact_list":
# Fetch all contacts (type='person') for this tenant
q = select(Contact).where(
Contact.tenant_id == tenant_id,
Contact.type == "person",
Contact.deleted_at.is_(None),
).order_by(Contact.displayname)
result = await db.execute(q)
contacts = result.scalars().all()
parameters["contacts"] = [
{
"name": f"{c.firstname or ''} {c.surname or ''}".strip() or c.displayname or "",
"email": c.email_1 or "",
"phone": c.phone_1 or "",
"type": c.type or "person",
"company": "", # contact persons are separate
}
for c in contacts
]
parameters["title"] = parameters.get("title", "Kontaktliste")
elif body.preset == "company_list":
# Fetch all companies (type='company') for this tenant
q = select(Contact).where(
Contact.tenant_id == tenant_id,
Contact.type == "company",
Contact.deleted_at.is_(None),
).order_by(Contact.name)
result = await db.execute(q)
companies = result.scalars().all()
parameters["companies"] = [
{
"name": c.name or c.displayname or "",
"address": (
f"{c.mailing_street or ''} {c.mailing_number or ''}".strip()
or f"{c.visit_street or ''} {c.visit_number or ''}".strip()
or ""
),
"zip": c.mailing_postalcode or "",
"city": c.mailing_city or "",
"phone": c.phone_1 or "",
"email": c.email_1 or "",
"contact_person": "", # could be extended with ContactPerson lookup
}
for c in companies
]
parameters["title"] = parameters.get("title", "Firmenliste")
elif body.preset == "audit_log":
# Fetch recent audit log entries for this tenant
q = select(AuditLog).where(
AuditLog.tenant_id == tenant_id,
).order_by(AuditLog.timestamp.desc()).limit(500)
result = await db.execute(q)
entries = result.scalars().all()
parameters["entries"] = [
{
"timestamp": str(e.timestamp) if e.timestamp else "",
"user": str(e.user_id) if e.user_id else "",
"action": e.action or "",
"entity": e.entity_type or "",
"entity_id": str(e.entity_id) if e.entity_id else "",
"details": str(e.changes) if e.changes else "",
}
for e in entries
]
parameters["title"] = parameters.get("title", "Audit-Log")
elif body.preset in ("calendar_week", "calendar_month"):
# Calendar presets: pass user params through (no DB model yet)
parameters.setdefault("title", "Wochenkalender" if body.preset == "calendar_week" else "Monatskalender")
parameters.setdefault("days", [])
parameters.setdefault("hours", [])
parameters.setdefault("weeks", [])
parameters.setdefault("weekday_names", [])
# Generate report
file_bytes, ext = generate_preset_report(
body.preset, body.output_format, body.parameters
body.preset, body.output_format, parameters
)
except Exception as exc:
raise HTTPException(
@@ -175,7 +263,7 @@ async def generate_preset(
},
) from exc
# Return file directly as streaming response (avoid DB tracking due to greenlet issues in test env)
# Return file directly as streaming response
import io as _io
media_types = {