feat(founding-wizard): Gründungs-Wizard für 2-Mann GmbH + 14 Notar-Templates

[migration-approved]

Templates (Migrations 123-136):
- 123 GO-GF (Geschäftsordnung Geschäftsführung)
- 124 SHA (Shareholders' Agreement, 56 Platzhalter)
- 125 Satzung (Articles of Association mit UG-Variante)
- 126 GF-Dienstvertrag (Trennungsprinzip Organ/Anstellung)
- 127 Arbeitsvertrag (AGG-neutral, NachwG, eAU)
- 128 Gesellschafterliste (§ 40 GmbHG)
- 129 GF-Bestellungsbeschluss (mit § 6 Abs. 2 Versicherung)
- 130 HRB-Anmeldung (§§ 7, 8, 39 GmbHG, § 12 HGB)
- 131 IP-Assignment Agreement (Gründer→GmbH)
- 132 Term Sheet (Pre-Seed/Seed VC-Standard)
- 133 Wandeldarlehensvertrag (Convertible Loan)
- 134 Beteiligungsvertrag (Subscription Agreement)
- 135 ESOP/VSOP-Plan (3 Varianten)
- 136 Cap Table

Kategorisierung (Migrations 137-138):
- ALTER TABLE compliance_legal_templates ADD lifecycle_stage TEXT[],
  functional_category TEXT (mit CHECK Constraints + GIN-Index)
- Backfill aller 105 Templates: lifecycle_stage (pre_founding|founding|
  startup|kmu|konzern) + functional_category (founding_legal|employment|
  investor_funding|...)

Backend Founding-Wizard Service:
- template_renderer.py: Handlebars-light ({{VAR}}, {{#IF FLAG}}...{{/IF}})
- wizard_to_context.py: Mapping Wizard-State → SCREAMING_SNAKE_CASE Vars
- markdown_to_docx.py: Markdown → DOCX via python-docx
- founding_wizard_routes.py: POST /v1/founding-wizard/generate
  → liefert base64-DOCX-Files für ausgewählte Templates

Frontend Founding-Wizard (/sdk/founding-wizard):
- 8-Step Wizard (Basics, Gesellschafter, GF, Kapital, Notar, SHA, GF-Verträge, Generate)
- useFoundingWizardForm Hook mit localStorage-Persistenz
- TypeScript Code-Registry (template-categories.ts) als Backup zur DB
- Word-Download via data:URLs (base64)

Tests:
- 20 Unit-Tests grün (Renderer, Context-Mapping, DOCX-Conversion)
- Playwright E2E-Test mit 2-Mann GmbH (Benjamin + Sharang) Test-Daten
This commit is contained in:
Benjamin Admin
2026-05-20 09:30:51 +02:00
parent 98ec6d4284
commit 7a5f1e48dd
33 changed files with 6725 additions and 0 deletions
@@ -0,0 +1,123 @@
/**
* Template-Kategorisierung als Code-Registry.
*
* Source-of-Truth bei aktiver Migration 137/138 ist die DB.
* Diese Registry dient als Fallback und für Frontend-only Filter,
* wenn DB-Felder noch nicht verfügbar sind (z.B. lokale Dev-DB ohne Migration).
*
* Synchron halten mit migrations/138_template_backfill_categories.sql.
*/
export type LifecycleStage = 'pre_founding' | 'founding' | 'startup' | 'kmu' | 'konzern'
export type FunctionalCategory =
| 'founding_legal'
| 'employment'
| 'investor_funding'
| 'customer_b2b'
| 'customer_b2c'
| 'data_protection'
| 'it_security'
| 'ai_governance'
| 'internal_policy'
| 'public_facing'
| 'compliance_process'
| 'finance_tax'
| 'vendor_supplier'
export interface TemplateCategorization {
lifecycle_stage: LifecycleStage[]
functional_category: FunctionalCategory
}
export const TEMPLATE_CATEGORIES: Record<string, TemplateCategorization> = {
// Founding Legal
gesellschafterliste: { lifecycle_stage: ['pre_founding', 'founding'], functional_category: 'founding_legal' },
gf_bestellungsbeschluss: { lifecycle_stage: ['founding'], functional_category: 'founding_legal' },
hrb_anmeldung: { lifecycle_stage: ['founding'], functional_category: 'founding_legal' },
ip_assignment_agreement: { lifecycle_stage: ['pre_founding', 'founding', 'startup'], functional_category: 'founding_legal' },
articles_of_association: { lifecycle_stage: ['founding', 'startup', 'kmu', 'konzern'], functional_category: 'founding_legal' },
sha: { lifecycle_stage: ['founding', 'startup', 'kmu', 'konzern'], functional_category: 'founding_legal' },
geschaeftsordnung_gf: { lifecycle_stage: ['founding', 'startup', 'kmu', 'konzern'], functional_category: 'founding_legal' },
// Investor / Funding
term_sheet: { lifecycle_stage: ['pre_founding', 'startup'], functional_category: 'investor_funding' },
convertible_loan_agreement: { lifecycle_stage: ['pre_founding', 'startup'], functional_category: 'investor_funding' },
subscription_agreement: { lifecycle_stage: ['startup', 'kmu'], functional_category: 'investor_funding' },
esop_plan: { lifecycle_stage: ['startup', 'kmu'], functional_category: 'investor_funding' },
cap_table: { lifecycle_stage: ['founding', 'startup', 'kmu', 'konzern'], functional_category: 'investor_funding' },
// Employment
managing_director_employment_contract: { lifecycle_stage: ['founding', 'startup', 'kmu', 'konzern'], functional_category: 'employment' },
employment_contract_de: { lifecycle_stage: ['founding', 'startup', 'kmu', 'konzern'], functional_category: 'employment' },
nda: { lifecycle_stage: ['founding', 'startup', 'kmu', 'konzern'], functional_category: 'employment' },
offboarding_policy: { lifecycle_stage: ['founding', 'startup', 'kmu', 'konzern'], functional_category: 'employment' },
// Customer B2B
agb: { lifecycle_stage: ['startup', 'kmu', 'konzern'], functional_category: 'customer_b2b' },
sla: { lifecycle_stage: ['startup', 'kmu', 'konzern'], functional_category: 'customer_b2b' },
dpa: { lifecycle_stage: ['startup', 'kmu', 'konzern'], functional_category: 'customer_b2b' },
data_processing_agreement: { lifecycle_stage: ['startup', 'kmu', 'konzern'], functional_category: 'customer_b2b' },
cloud_service_agreement: { lifecycle_stage: ['startup', 'kmu', 'konzern'], functional_category: 'customer_b2b' },
terms_of_service: { lifecycle_stage: ['startup', 'kmu', 'konzern'], functional_category: 'customer_b2b' },
// Public-facing
impressum: { lifecycle_stage: ['founding', 'startup', 'kmu', 'konzern'], functional_category: 'public_facing' },
// AI Governance
ai_usage_policy: { lifecycle_stage: ['startup', 'kmu', 'konzern'], functional_category: 'ai_governance' },
// Whistleblower nur ab KMU (>=50 MA)
whistleblower_policy: { lifecycle_stage: ['kmu', 'konzern'], functional_category: 'internal_policy' },
}
/**
* Notartermin-Bundle: alle Dokumente die für die Gründung benötigt werden.
* Investor-Dokumente sind separat (term_sheet, convertible_loan_agreement, etc.).
*/
export const NOTARY_BUNDLE_DOCUMENTS: string[] = [
'articles_of_association', // Satzung — notariell beurkundet
'gesellschafterliste', // Pflicht § 40 GmbHG
'gf_bestellungsbeschluss', // Bestellung Geschäftsführer
'hrb_anmeldung', // HRB-Anmeldung
'sha', // optional parallel
'geschaeftsordnung_gf', // intern, nach Notar
'managing_director_employment_contract', // GF-Dienstverträge
'ip_assignment_agreement', // Gründer-IP sichern
]
export function getDocumentsForStage(stage: LifecycleStage): string[] {
return Object.entries(TEMPLATE_CATEGORIES)
.filter(([, cat]) => cat.lifecycle_stage.includes(stage))
.map(([docType]) => docType)
}
export function getDocumentsForCategory(category: FunctionalCategory): string[] {
return Object.entries(TEMPLATE_CATEGORIES)
.filter(([, cat]) => cat.functional_category === category)
.map(([docType]) => docType)
}
export const LIFECYCLE_STAGE_LABELS: Record<LifecycleStage, string> = {
pre_founding: 'Vor-Gründung (Term Sheet, IP-Sicherung)',
founding: 'Gründung (Notar)',
startup: 'Startup (0-3 Jahre, <25 MA)',
kmu: 'KMU (3+ Jahre, 25-250 MA)',
konzern: 'Konzern (250+ MA)',
}
export const FUNCTIONAL_CATEGORY_LABELS: Record<FunctionalCategory, string> = {
founding_legal: 'Gründungsrechtliches',
employment: 'Arbeitsverträge',
investor_funding: 'Investor & Funding',
customer_b2b: 'Kunden-Verträge (B2B)',
customer_b2c: 'Kunden-Verträge (B2C)',
data_protection: 'Datenschutz (DSGVO)',
it_security: 'IT-Sicherheit',
ai_governance: 'KI-Governance',
internal_policy: 'Interne Richtlinien',
public_facing: 'Öffentlich (Website)',
compliance_process:'Compliance-Prozesse',
finance_tax: 'Finanzen & Steuern',
vendor_supplier: 'Lieferanten',
}
+184
View File
@@ -0,0 +1,184 @@
/**
* TypeScript-Datentypen für den Founding-Wizard.
*
* Die Wizard-Eingaben werden in localStorage gespeichert und beim Submit
* an die document-generator API geschickt zur Template-Befüllung.
*/
import type { LifecycleStage } from './template-categories'
export interface Gesellschafter {
id: string
rolle: 'founder' | 'investor' | 'family' | 'other'
name: string
geburtsdatum?: string // YYYY-MM-DD
adresse: string
email?: string
/** Nennbetrag in EUR, z.B. 25000 */
nennbetrag_eur: number
/** Anteilsnummer beginnend bei 1 */
anteil_nr: number
/** prozentualer Anteil am Stammkapital (computed) */
anteil_pct?: number
is_geschaeftsfuehrer: boolean
/** Bei GF: interne Rolle z.B. CEO/CTO */
internal_role?: string
/** Falls Gründer akademischen Hintergrund hat (Professur etc.) */
has_academic_background?: boolean
}
export interface NotarData {
notary_name: string
notary_place: string
notary_address?: string
notary_email?: string
notarial_date?: string // YYYY-MM-DD, geplant
urnr?: string // wird vom Notar vergeben
}
export interface CompanyBasics {
company_name: string
legal_form: 'GmbH' | 'UG'
company_seat: string // z.B. "Bietigheim-Bissingen"
company_address: string
company_purpose_description: string // Volltext für § 2 Satzung
company_purpose_bullets: string[]
industry: string
business_year: string // z.B. "Kalenderjahr"
has_research_focus: boolean
}
export interface CapitalConfig {
stammkapital_eur: number // z.B. 25000
einlage_method: 'Geld' | 'Sacheinlage' | 'Geld und Sacheinlage'
einlage_quote_initial_pct: number // z.B. 50 oder 100
has_sacheinlage: boolean
}
export interface SHAConfig {
has_sha: boolean
vesting_months: number // Standard 48
cliff_months: number // Standard 12
drag_along_threshold_pct: number // Standard 75
tag_along_threshold_pct: number // Standard 20
reserved_matters_majority_pct: number // Standard 75
has_beirat: boolean
has_texas_shootout: boolean
has_ceo_designation: boolean
ceo_name?: string // ref to gesellschafter.name
esop_pool_pct: number // Standard 0 oder 10
}
export interface GFContract {
gesellschafter_id: string // ref to gesellschafter.id
gross_annual_salary_eur: number
has_bonus: boolean
has_company_car: boolean
has_bav: boolean
vacation_days: number // Standard 30
kuendigungsfrist_gesellschaft_monate: number // Standard 6
kuendigungsfrist_gf_monate: number // Standard 3
para_181_release: boolean
sv_status: 'sozialversicherungsfrei' | 'sozialversicherungspflichtig' | 'noch zu klären'
}
/**
* Vollständiger Wizard-State.
* Wird Step-by-Step befüllt, in localStorage gespeichert,
* und beim Submit an /api/v1/founding-wizard/generate geschickt.
*/
export interface FoundingWizardState {
/** Aktueller Step (1-8) */
current_step: number
/** Lifecycle-Stage Auswahl (default: founding) */
lifecycle_stage: LifecycleStage
// Step 1: Lifecycle
is_pre_notary: boolean
// Step 2: Basics
basics: CompanyBasics
// Step 3: Gesellschafter
gesellschafter: Gesellschafter[]
// Step 4: Kapital
capital: CapitalConfig
// Step 5: Notar
notar: NotarData
// Step 6: SHA-Konfiguration
sha: SHAConfig
// Step 7: GF-Verträge (1 pro GF)
gf_contracts: GFContract[]
// Step 8: Auswahl der zu generierenden Dokumente
selected_documents: string[]
/** Output nach Submit: URL + Dateiname pro generiertem Dokument */
generated_documents?: GeneratedDocument[]
}
export interface GeneratedDocument {
document_type: string
title: string
download_url: string
size_bytes: number
generated_at: string
}
/** Default-State für einen frischen Wizard */
export function defaultFoundingWizardState(): FoundingWizardState {
return {
current_step: 1,
lifecycle_stage: 'founding',
is_pre_notary: true,
basics: {
company_name: '',
legal_form: 'GmbH',
company_seat: '',
company_address: '',
company_purpose_description: '',
company_purpose_bullets: [],
industry: '',
business_year: 'Kalenderjahr',
has_research_focus: false,
},
gesellschafter: [],
capital: {
stammkapital_eur: 25000,
einlage_method: 'Geld',
einlage_quote_initial_pct: 50,
has_sacheinlage: false,
},
notar: {
notary_name: '',
notary_place: '',
},
sha: {
has_sha: true,
vesting_months: 48,
cliff_months: 12,
drag_along_threshold_pct: 75,
tag_along_threshold_pct: 20,
reserved_matters_majority_pct: 75,
has_beirat: false,
has_texas_shootout: false,
has_ceo_designation: false,
esop_pool_pct: 0,
},
gf_contracts: [],
selected_documents: [
'articles_of_association',
'gesellschafterliste',
'gf_bestellungsbeschluss',
'hrb_anmeldung',
'sha',
'geschaeftsordnung_gf',
'managing_director_employment_contract',
'ip_assignment_agreement',
],
}
}