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
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:
@@ -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.
|
||||||
|
|||||||
@@ -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 }> = {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user