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:
@@ -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',
|
||||
}
|
||||
@@ -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',
|
||||
],
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user