diff --git a/admin-compliance/app/sdk/company-profile/page.tsx b/admin-compliance/app/sdk/company-profile/page.tsx index 836d1d7..18c0282 100644 --- a/admin-compliance/app/sdk/company-profile/page.tsx +++ b/admin-compliance/app/sdk/company-profile/page.tsx @@ -22,7 +22,6 @@ import { AI_INTEGRATION_TYPE_LABELS, HUMAN_OVERSIGHT_LABELS, CRITICAL_SECTOR_LABELS, - SDKCoverageAssessment, } from '@/lib/sdk/types' // ============================================================================= @@ -103,60 +102,6 @@ const MACHINE_BUILDER_INDUSTRIES = [ const isMachineBuilderIndustry = (industry: string) => MACHINE_BUILDER_INDUSTRIES.includes(industry) -// ============================================================================= -// HELPER: ASSESS SDK COVERAGE -// ============================================================================= - -function assessSDKCoverage(profile: Partial): SDKCoverageAssessment { - const coveredRegulations: string[] = ['DSGVO', 'BDSG', 'TTDSG', 'AI Act'] - const partiallyCoveredRegulations: string[] = [] - const notCoveredRegulations: string[] = [] - const reasons: string[] = [] - const recommendations: string[] = [] - - // Check target markets - const targetMarkets = profile.targetMarkets || [] - - if (targetMarkets.includes('worldwide')) { - notCoveredRegulations.push('CCPA (Kalifornien)', 'LGPD (Brasilien)', 'POPIA (Südafrika)') - reasons.push('Weltweiter Betrieb erfordert Kenntnisse lokaler Datenschutzgesetze') - recommendations.push('Für außereuropäische Märkte empfehlen wir die Konsultation lokaler Rechtsanwälte') - } - - if (targetMarkets.includes('eu_uk')) { - partiallyCoveredRegulations.push('UK GDPR', 'UK AI Framework') - reasons.push('UK-Recht weicht nach Brexit teilweise von EU-Recht ab') - recommendations.push('Prüfen Sie UK-spezifische Anpassungen Ihrer Datenschutzerklärung') - } - - // Check company size - if (profile.companySize === 'enterprise' || profile.companySize === 'large') { - coveredRegulations.push('NIS2') - reasons.push('Als größeres Unternehmen können NIS2-Pflichten relevant sein') - } - - // Check offerings - const offerings = profile.offerings || [] - if (offerings.includes('webshop')) { - coveredRegulations.push('Fernabsatzrecht') - recommendations.push('Widerrufsbelehrung und AGB-Generator sind im SDK enthalten') - } - - // Determine if fully covered - const requiresLegalCounsel = notCoveredRegulations.length > 0 || targetMarkets.includes('worldwide') - const isFullyCovered = !requiresLegalCounsel && notCoveredRegulations.length === 0 - - return { - isFullyCovered, - coveredRegulations, - partiallyCoveredRegulations, - notCoveredRegulations, - requiresLegalCounsel, - reasons, - recommendations, - } -} - // ============================================================================= // STEP COMPONENTS // ============================================================================= @@ -178,7 +123,7 @@ function StepBasicInfo({ type="text" value={data.companyName || ''} onChange={e => onChange({ companyName: e.target.value })} - placeholder="Ihre Firma GmbH" + placeholder="Ihre Firma (ohne Rechtsform)" className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" /> @@ -222,9 +167,15 @@ function StepBasicInfo({ onChange({ foundedYear: parseInt(e.target.value) || null })} + onChange={e => { + const val = parseInt(e.target.value) + onChange({ foundedYear: isNaN(val) ? null : val }) + }} + onFocus={e => { + if (!data.foundedYear) onChange({ foundedYear: 2000 }) + }} placeholder="2020" - min="1800" + min="1900" max={new Date().getFullYear()} className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" /> @@ -233,6 +184,27 @@ function StepBasicInfo({ ) } +// URL fields shown when specific offerings are selected +const OFFERING_URL_CONFIG: Partial> = { + website: { label: 'Website-Domain', placeholder: 'https://www.beispiel.de', hint: 'Ihre Unternehmenswebsite' }, + webshop: { label: 'Online-Shop URL', placeholder: 'https://shop.beispiel.de', hint: 'URL zu Ihrem Online-Shop' }, + app_mobile: { label: 'App-Store Links', placeholder: 'https://apps.apple.com/... oder https://play.google.com/...', hint: 'Apple App Store und/oder Google Play Store Link' }, + software_saas: { label: 'SaaS-Portal URL', placeholder: 'https://app.beispiel.de', hint: 'Login-/Registrierungsseite Ihres Kundenportals' }, + app_web: { label: 'Web-App URL', placeholder: 'https://app.beispiel.de', hint: 'URL zu Ihrer Web-Anwendung' }, +} + +// Step-specific explanations for "Warum diese Fragen?" +const STEP_EXPLANATIONS: Record = { + 1: 'Rechtsform und Gründungsjahr bestimmen, welche Meldepflichten und Schwellenwerte für Ihr Unternehmen gelten (z.B. NIS2, AI Act).', + 2: 'Ihr Geschäftsmodell und Ihre Angebote bestimmen, welche DSGVO-Pflichten greifen: B2C erfordert z.B. strengere Einwilligungsregeln, Webshops brauchen Cookie-Banner und Datenschutzerklärungen, SaaS-Angebote eine Auftragsverarbeitung.', + 3: 'Die Unternehmensgröße bestimmt, ob Sie einen DSB benennen müssen (ab 20 MA), ob NIS2-Pflichten greifen und welche Audit-Anforderungen gelten.', + 4: 'Standorte und Zielmärkte bestimmen, welche nationalen Datenschutzgesetze zusätzlich zur DSGVO greifen (z.B. BDSG, DSG-AT, UK GDPR, CCPA).', + 5: 'Ob Sie Verantwortlicher oder Auftragsverarbeiter sind, bestimmt Ihre DSGVO-Pflichten grundlegend. KI-Nutzung löst zusätzliche AI-Act-Pflichten aus.', + 6: 'Ihre IT-Systeme und KI-Anwendungen werden für das Verarbeitungsverzeichnis (VVT), die technisch-organisatorischen Maßnahmen (TOM) und die KI-Risikobewertung benötigt.', + 7: 'Regulierungsrahmen und Prüfzyklen definieren, welche Compliance-Module für Sie aktiviert werden und in welchem Rhythmus Audits stattfinden.', + 8: 'Als Maschinenbauer gelten zusätzliche Anforderungen: CE-Kennzeichnung, Maschinenverordnung, Produktsicherheit und ggf. Hochrisiko-KI im Sinne des AI Act.', +} + function StepBusinessModel({ data, onChange, @@ -243,19 +215,29 @@ function StepBusinessModel({ const toggleOffering = (offering: OfferingType) => { const current = data.offerings || [] if (current.includes(offering)) { - onChange({ offerings: current.filter(o => o !== offering) }) + // Remove offering and its URL + const urls = { ...(data.offeringUrls || {}) } + delete urls[offering] + onChange({ offerings: current.filter(o => o !== offering), offeringUrls: urls }) } else { onChange({ offerings: [...current, offering] }) } } + const updateOfferingUrl = (offering: string, url: string) => { + onChange({ offeringUrls: { ...(data.offeringUrls || {}), [offering]: url } }) + } + + // Offerings that are selected and have URL config + const selectedWithUrls = (data.offerings || []).filter(o => o in OFFERING_URL_CONFIG) + return (
-
+
{Object.entries(BUSINESS_MODEL_LABELS).map(([value, label]) => ( ))}
@@ -298,6 +280,31 @@ function StepBusinessModel({ ))}
+ + {/* URL fields for selected offerings */} + {selectedWithUrls.length > 0 && ( +
+ + {selectedWithUrls.map(offering => { + const config = OFFERING_URL_CONFIG[offering]! + return ( +
+ + updateOfferingUrl(offering, e.target.value)} + placeholder={config.placeholder} + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent" + /> +

{config.hint}

+
+ ) + })} +
+ )}
) } @@ -1234,134 +1241,6 @@ function StepMachineBuilder({ // COVERAGE ASSESSMENT COMPONENT // ============================================================================= -function CoverageAssessmentPanel({ profile }: { profile: Partial }) { - const assessment = assessSDKCoverage(profile) - - return ( -
-

SDK-Abdeckung

- - {/* Status */} -
-
- {assessment.isFullyCovered ? ( - <> - - - - Vollständig durch SDK abgedeckt - - ) : ( - <> - - - - Teilweise Einschränkungen - - )} -
-
- - {/* Covered Regulations */} - {assessment.coveredRegulations.length > 0 && ( -
-
Abgedeckte Regulierungen
-
- {assessment.coveredRegulations.map(reg => ( - - {reg} - - ))} -
-
- )} - - {/* Not Covered */} - {assessment.notCoveredRegulations.length > 0 && ( -
-
Nicht abgedeckt
-
- {assessment.notCoveredRegulations.map(reg => ( - - {reg} - - ))} -
-
- )} - - {/* Recommendations */} - {assessment.recommendations.length > 0 && ( -
-
Empfehlungen
-
    - {assessment.recommendations.map((rec, i) => ( -
  • - - {rec} -
  • - ))} -
-
- )} - - {/* Legal Counsel Warning */} - {assessment.requiresLegalCounsel && ( -
-
- - - -
-
Rechtsberatung empfohlen
-
- Basierend auf Ihrem Profil empfehlen wir die Konsultation eines spezialisierten - Rechtsanwalts für Bereiche, die über den Scope dieses SDKs hinausgehen. -
-
-
-
- )} -
- ) -} - // ============================================================================= // GENERATE DOCUMENTS BUTTON // ============================================================================= @@ -1443,6 +1322,7 @@ export default function CompanyProfilePage() { foundedYear: null, businessModel: undefined, offerings: [], + offeringUrls: {}, companySize: undefined, employeeCount: '', annualRevenue: '', @@ -1486,6 +1366,7 @@ export default function CompanyProfilePage() { foundedYear: data.founded_year || undefined, businessModel: data.business_model || undefined, offerings: data.offerings || [], + offeringUrls: data.offering_urls || {}, companySize: data.company_size || undefined, employeeCount: data.employee_count || '', annualRevenue: data.annual_revenue || '', @@ -1552,6 +1433,7 @@ export default function CompanyProfilePage() { founded_year: formData.foundedYear || null, business_model: formData.businessModel || 'B2B', offerings: formData.offerings || [], + offering_urls: formData.offeringUrls || {}, company_size: formData.companySize || 'small', employee_count: formData.employeeCount || '', annual_revenue: formData.annualRevenue || '', @@ -1695,6 +1577,7 @@ export default function CompanyProfilePage() { foundedYear: null, businessModel: undefined, offerings: [], + offeringUrls: {}, companySize: undefined, employeeCount: '', annualRevenue: '', @@ -1894,15 +1777,13 @@ export default function CompanyProfilePage() { )} - {/* Sidebar: Coverage Assessment */} + {/* Sidebar */}
- - - {/* Info Box */} -
+ {/* Step-specific explanation */} +
Warum diese Fragen?
- Diese Informationen helfen uns, die für Ihr Unternehmen relevanten Regulierungen - zu identifizieren und ehrlich zu kommunizieren, wo unsere Grenzen liegen. + {STEP_EXPLANATIONS[currentStep] || 'Diese Informationen helfen uns, die für Ihr Unternehmen relevanten Regulierungen zu identifizieren.'}
@@ -1933,18 +1813,6 @@ export default function CompanyProfilePage() {
)} - - {/* Delete Profile Button */} - {formData.companyName && ( -
- -
- )}
diff --git a/admin-compliance/lib/sdk/types.ts b/admin-compliance/lib/sdk/types.ts index 2057b7d..b91273f 100644 --- a/admin-compliance/lib/sdk/types.ts +++ b/admin-compliance/lib/sdk/types.ts @@ -27,7 +27,7 @@ export type CustomerType = 'new' | 'existing' // COMPANY PROFILE (Business Context - collected before use cases) // ============================================================================= -export type BusinessModel = 'B2B' | 'B2C' | 'B2B_B2C' +export type BusinessModel = 'B2B' | 'B2C' | 'B2B_B2C' | 'B2B2C' export type OfferingType = | 'app_mobile' // Mobile App @@ -154,6 +154,7 @@ export interface CompanyProfile { // Business Model businessModel: BusinessModel offerings: OfferingType[] + offeringUrls: Partial> // e.g. { website: 'https://...', webshop: 'https://...' } // Size & Scope companySize: CompanySize @@ -204,6 +205,7 @@ export const BUSINESS_MODEL_LABELS: Record = { B2B: 'B2B (Geschäftskunden)', B2C: 'B2C (Privatkunden)', B2B_B2C: 'B2B und B2C', + B2B2C: 'B2B2C (über Partner an Endkunden)', } export const OFFERING_TYPE_LABELS: Record = { diff --git a/backend-compliance/compliance/api/generation_routes.py b/backend-compliance/compliance/api/generation_routes.py index a8b787a..cce3719 100644 --- a/backend-compliance/compliance/api/generation_routes.py +++ b/backend-compliance/compliance/api/generation_routes.py @@ -48,7 +48,8 @@ def _get_template_context(db, tid: str) -> dict: resp = row_to_response(row) # Build flat context return { - "company_name": resp.company_name, + "company_name": f"{resp.company_name} {resp.legal_form}".strip() if resp.legal_form else resp.company_name, + "company_name_short": resp.company_name, "legal_form": resp.legal_form, "industry": resp.industry, "business_model": resp.business_model,