fix(backend): SQLAlchemy text() fuer alle raw SQL + UI-Verbesserungen
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Failing after 32s
CI / test-python-backend-compliance (push) Successful in 39s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 18s

- CRITICAL: Alle db.execute() Aufrufe in company_profile_routes.py
  und generation_routes.py mit text() gewrapped (SQLAlchemy 2.x)
- Geschaeftsmodell-Kacheln: Nur Kurztext, Beschreibung bei Klick
- "Warum diese Fragen" in Hauptbereich unter Ueberschrift verschoben
- Sidebar-Box entfernt fuer mehr Platz

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-08 22:53:29 +01:00
parent 2abf0b4cac
commit 53ff0722a4
4 changed files with 31 additions and 49 deletions

View File

@@ -237,8 +237,8 @@ function StepBusinessModel({
<label className="block text-sm font-medium text-gray-700 mb-4"> <label className="block text-sm font-medium text-gray-700 mb-4">
Geschäftsmodell <span className="text-red-500">*</span> Geschäftsmodell <span className="text-red-500">*</span>
</label> </label>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4"> <div className="grid grid-cols-4 gap-4">
{Object.entries(BUSINESS_MODEL_LABELS).map(([value, label]) => ( {Object.entries(BUSINESS_MODEL_LABELS).map(([value, { short }]) => (
<button <button
key={value} key={value}
type="button" type="button"
@@ -249,13 +249,15 @@ function StepBusinessModel({
: 'border-gray-200 hover:border-purple-300' : 'border-gray-200 hover:border-purple-300'
}`} }`}
> >
<div className="text-2xl mb-2"> <div className="font-semibold">{short}</div>
{value === 'B2B' ? '🏢' : value === 'B2C' ? '👥' : value === 'B2B2C' ? '🏢🔗👥' : '🏢👥'}
</div>
<div className="font-medium text-sm">{label}</div>
</button> </button>
))} ))}
</div> </div>
{data.businessModel && (
<p className="text-sm text-gray-500 mt-2">
{BUSINESS_MODEL_LABELS[data.businessModel].description}
</p>
)}
</div> </div>
<div> <div>
@@ -1706,6 +1708,9 @@ export default function CompanyProfilePage() {
{(wizardSteps.find(s => s.id === currentStep) || wizardSteps[0]).name} {(wizardSteps.find(s => s.id === currentStep) || wizardSteps[0]).name}
</h2> </h2>
<p className="text-gray-500">{(wizardSteps.find(s => s.id === currentStep) || wizardSteps[0]).description}</p> <p className="text-gray-500">{(wizardSteps.find(s => s.id === currentStep) || wizardSteps[0]).description}</p>
{STEP_EXPLANATIONS[currentStep] && (
<p className="text-sm text-blue-600 mt-2">{STEP_EXPLANATIONS[currentStep]}</p>
)}
</div> </div>
{currentStep === 1 && <StepBasicInfo data={formData} onChange={updateFormData} />} {currentStep === 1 && <StepBasicInfo data={formData} onChange={updateFormData} />}
@@ -1779,33 +1784,9 @@ export default function CompanyProfilePage() {
{/* Sidebar */} {/* Sidebar */}
<div className="lg:col-span-1"> <div className="lg:col-span-1">
{/* Step-specific explanation */}
<div className="bg-blue-50 rounded-xl border border-blue-200 p-6">
<div className="flex items-start gap-3">
<svg
className="w-5 h-5 text-blue-600 mt-0.5 flex-shrink-0"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<div>
<div className="font-medium text-blue-800">Warum diese Fragen?</div>
<div className="text-sm text-blue-700 mt-1">
{STEP_EXPLANATIONS[currentStep] || 'Diese Informationen helfen uns, die für Ihr Unternehmen relevanten Regulierungen zu identifizieren.'}
</div>
</div>
</div>
</div>
{/* Generate Documents CTA */} {/* Generate Documents CTA */}
{formData.isComplete && ( {formData.isComplete && (
<div className="mt-6 bg-gradient-to-br from-purple-50 to-indigo-50 rounded-xl border border-purple-200 p-6"> <div className="bg-gradient-to-br from-purple-50 to-indigo-50 rounded-xl border border-purple-200 p-6">
<h3 className="font-semibold text-purple-900 mb-2">Dokumente generieren</h3> <h3 className="font-semibold text-purple-900 mb-2">Dokumente generieren</h3>
<p className="text-sm text-purple-700 mb-4"> <p className="text-sm text-purple-700 mb-4">
Basierend auf Ihrem Profil können DSFA, VVT, TOM, Löschfristen und Pflichten automatisch als Entwürfe generiert werden. Basierend auf Ihrem Profil können DSFA, VVT, TOM, Löschfristen und Pflichten automatisch als Entwürfe generiert werden.

View File

@@ -201,11 +201,11 @@ export const COMPANY_SIZE_LABELS: Record<CompanySize, string> = {
enterprise: 'Konzern (1000+ MA)', enterprise: 'Konzern (1000+ MA)',
} }
export const BUSINESS_MODEL_LABELS: Record<BusinessModel, string> = { export const BUSINESS_MODEL_LABELS: Record<BusinessModel, { short: string; description: string }> = {
B2B: 'B2B (Geschäftskunden)', B2B: { short: 'B2B', description: 'Verkauf an Geschäftskunden' },
B2C: 'B2C (Privatkunden)', B2C: { short: 'B2C', description: 'Verkauf an Privatkunden' },
B2B_B2C: 'B2B und B2C', B2B_B2C: { short: 'B2B + B2C', description: 'Verkauf an Geschäfts- und Privatkunden' },
B2B2C: 'B2B2C (über Partner an Endkunden)', B2B2C: { short: 'B2B2C', description: 'Über Partner an Endkunden (z.B. Plattform, White-Label)' },
} }
export const OFFERING_TYPE_LABELS: Record<OfferingType, { label: string; description: string }> = { export const OFFERING_TYPE_LABELS: Record<OfferingType, { label: string; description: string }> = {

View File

@@ -15,6 +15,7 @@ from typing import Optional
from fastapi import APIRouter, HTTPException, Header from fastapi import APIRouter, HTTPException, Header
from pydantic import BaseModel from pydantic import BaseModel
from sqlalchemy import text
from database import SessionLocal from database import SessionLocal
@@ -224,9 +225,9 @@ def log_audit(db, tenant_id: str, action: str, changed_fields: Optional[dict], c
"""Write an audit log entry.""" """Write an audit log entry."""
try: try:
db.execute( db.execute(
"""INSERT INTO compliance_company_profile_audit text("""INSERT INTO compliance_company_profile_audit
(tenant_id, action, changed_fields, changed_by) (tenant_id, action, changed_fields, changed_by)
VALUES (:tenant_id, :action, :fields::jsonb, :changed_by)""", VALUES (:tenant_id, :action, :fields::jsonb, :changed_by)"""),
{ {
"tenant_id": tenant_id, "tenant_id": tenant_id,
"action": action, "action": action,
@@ -252,7 +253,7 @@ async def get_company_profile(
db = SessionLocal() db = SessionLocal()
try: try:
result = db.execute( result = db.execute(
f"SELECT {_BASE_COLUMNS} FROM compliance_company_profiles WHERE tenant_id = :tenant_id", text(f"SELECT {_BASE_COLUMNS} FROM compliance_company_profiles WHERE tenant_id = :tenant_id"),
{"tenant_id": tid}, {"tenant_id": tid},
) )
row = result.fetchone() row = result.fetchone()
@@ -276,7 +277,7 @@ async def upsert_company_profile(
try: try:
# Check if profile exists # Check if profile exists
existing = db.execute( existing = db.execute(
"SELECT id FROM compliance_company_profiles WHERE tenant_id = :tid", text("SELECT id FROM compliance_company_profiles WHERE tenant_id = :tid"),
{"tid": tid}, {"tid": tid},
).fetchone() ).fetchone()
@@ -285,7 +286,7 @@ async def upsert_company_profile(
completed_at_clause = ", completed_at = NOW()" if profile.is_complete else ", completed_at = NULL" completed_at_clause = ", completed_at = NOW()" if profile.is_complete else ", completed_at = NULL"
db.execute( db.execute(
f"""INSERT INTO compliance_company_profiles text(f"""INSERT INTO compliance_company_profiles
(tenant_id, company_name, legal_form, industry, founded_year, (tenant_id, company_name, legal_form, industry, founded_year,
business_model, offerings, company_size, employee_count, annual_revenue, business_model, offerings, company_size, employee_count, annual_revenue,
headquarters_country, headquarters_city, has_international_locations, headquarters_country, headquarters_city, has_international_locations,
@@ -344,7 +345,7 @@ async def upsert_company_profile(
supervisory_authority = EXCLUDED.supervisory_authority, supervisory_authority = EXCLUDED.supervisory_authority,
review_cycle_months = EXCLUDED.review_cycle_months, review_cycle_months = EXCLUDED.review_cycle_months,
updated_at = NOW() updated_at = NOW()
{completed_at_clause}""", {completed_at_clause}"""),
{ {
"tid": tid, "tid": tid,
"company_name": profile.company_name, "company_name": profile.company_name,
@@ -392,7 +393,7 @@ async def upsert_company_profile(
# Fetch and return # Fetch and return
result = db.execute( result = db.execute(
f"SELECT {_BASE_COLUMNS} FROM compliance_company_profiles WHERE tenant_id = :tid", text(f"SELECT {_BASE_COLUMNS} FROM compliance_company_profiles WHERE tenant_id = :tid"),
{"tid": tid}, {"tid": tid},
) )
row = result.fetchone() row = result.fetchone()
@@ -415,7 +416,7 @@ async def delete_company_profile(
db = SessionLocal() db = SessionLocal()
try: try:
existing = db.execute( existing = db.execute(
"SELECT id FROM compliance_company_profiles WHERE tenant_id = :tid", text("SELECT id FROM compliance_company_profiles WHERE tenant_id = :tid"),
{"tid": tid}, {"tid": tid},
).fetchone() ).fetchone()
@@ -423,7 +424,7 @@ async def delete_company_profile(
raise HTTPException(status_code=404, detail="Company profile not found") raise HTTPException(status_code=404, detail="Company profile not found")
db.execute( db.execute(
"DELETE FROM compliance_company_profiles WHERE tenant_id = :tid", text("DELETE FROM compliance_company_profiles WHERE tenant_id = :tid"),
{"tid": tid}, {"tid": tid},
) )
@@ -451,7 +452,7 @@ async def get_template_context(
db = SessionLocal() db = SessionLocal()
try: try:
result = db.execute( result = db.execute(
f"SELECT {_BASE_COLUMNS} FROM compliance_company_profiles WHERE tenant_id = :tid", text(f"SELECT {_BASE_COLUMNS} FROM compliance_company_profiles WHERE tenant_id = :tid"),
{"tid": tid}, {"tid": tid},
) )
row = result.fetchone() row = result.fetchone()
@@ -513,11 +514,11 @@ async def get_audit_log(
db = SessionLocal() db = SessionLocal()
try: try:
result = db.execute( result = db.execute(
"""SELECT id, action, changed_fields, changed_by, created_at text("""SELECT id, action, changed_fields, changed_by, created_at
FROM compliance_company_profile_audit FROM compliance_company_profile_audit
WHERE tenant_id = :tid WHERE tenant_id = :tid
ORDER BY created_at DESC ORDER BY created_at DESC
LIMIT 100""", LIMIT 100"""),
{"tid": tid}, {"tid": tid},
) )
rows = result.fetchall() rows = result.fetchall()

View File

@@ -39,7 +39,7 @@ def _get_template_context(db, tid: str) -> dict:
cp_db = SessionLocal() cp_db = SessionLocal()
try: try:
result = cp_db.execute( result = cp_db.execute(
f"SELECT {_BASE_COLUMNS} FROM compliance_company_profiles WHERE tenant_id = :tid", text(f"SELECT {_BASE_COLUMNS} FROM compliance_company_profiles WHERE tenant_id = :tid"),
{"tid": tid}, {"tid": tid},
) )
row = result.fetchone() row = result.fetchone()