7335f64f4f
CI / loc-budget (push) Failing after 20s
CI / detect-changes (push) Successful in 12s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / validate-canonical-controls (push) Successful in 19s
CI / nodejs-build (push) Successful in 3m17s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Successful in 43s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
Wizard unterstuetzt jetzt 2-4 Gesellschafter mit individuellem IP-Bereich: - Pro Gruender ein IP-Assignment-Vertrag (z.B. Benjamin: Compliance+RAG; Sharang: Security+Infrastruktur). Pro GF ein eigener Dienstvertrag. - Step 1: Prefill-Button aus Unternehmensprofil + Felder Registergericht und HRB-Nr. - Step 2: Rollen-Dropdown (CEO/CTO/CFO/COO/CPO/GF/Sonstige) statt freie Texteingabe, IP-Bereiche-Textarea pro Person. Backend: - generate_documents() iteriert pro Person fuer PER_PERSON_DOCS. - _build_person_context() injiziert ASSIGNOR_*, GF_*, IP_LIST_DETAILS aus person.ip_areas. - base_context() propagiert basics.register_court und basics.hrb_number. Tests: - 30/30 Pytest gruen (6 neue: Per-Person-Context, Slug-Helper, Registergericht-Propagation). - 4 neue Playwright-E2E-Specs (hermetisch via route.fulfill, mit Console-/Page-Error-Traps): kompletter 8-Step-Flow, Prefill-Fehlerpfad, Step-Navigation/Reset, Rollen-Dropdown + IP-Areas. - Spec setzt 'bp-sdk-cookie-consent' im addInitScript damit der CookieBannerOverlay nicht die Wizard-Buttons ueberlagert. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
396 lines
18 KiB
Python
396 lines
18 KiB
Python
"""
|
|
Mapping vom Wizard-State (frontend) auf den Template-Context (Render-Variablen).
|
|
|
|
Frontend liefert ein JSON-Payload mit den Wizard-Schritten. Hier konvertieren
|
|
wir es in eine flache Dict-Struktur, deren Keys SCREAMING_SNAKE_CASE sind und
|
|
zu den Platzhaltern in den Templates passen (z.B. {{COMPANY_NAME}}).
|
|
|
|
Pro Dokumenttyp (document_type) wird der jeweils benoetigte Subset gebaut.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
|
|
def _gs_table(gesellschafter: list[dict[str, Any]], stammkapital: int) -> str:
|
|
"""Erzeugt eine Markdown-Tabelle der Gesellschafter."""
|
|
rows = []
|
|
for g in gesellschafter:
|
|
nb = int(g.get("nennbetrag_eur") or 0)
|
|
pct = (nb / max(stammkapital, 1)) * 100 if stammkapital else 0
|
|
rows.append(
|
|
f"| {g.get('anteil_nr', '')} | {g.get('name', '')} | "
|
|
f"{g.get('geburtsdatum') or g.get('adresse', '')} | "
|
|
f"{g.get('adresse', '')} | {g.get('anteil_nr', '')} | "
|
|
f"{nb:,} | {pct:.2f}% |".replace(",", ".")
|
|
)
|
|
return "\n".join(rows)
|
|
|
|
|
|
def _parties_list(gesellschafter: list[dict[str, Any]]) -> str:
|
|
"""Aufzaehlung der Parteien fuer SHA, IP-Assignment etc."""
|
|
lines = []
|
|
for idx, g in enumerate(gesellschafter):
|
|
letter = chr(ord("a") + idx)
|
|
line = f"{letter}) **{g.get('name', '')}**"
|
|
if g.get("geburtsdatum"):
|
|
line += f", geboren am {g['geburtsdatum']}"
|
|
if g.get("adresse"):
|
|
line += f", wohnhaft in {g['adresse']}"
|
|
lines.append(line + ",")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def _parties_list_with_shares(gesellschafter: list[dict[str, Any]]) -> str:
|
|
"""Erzeugt nummerierte Liste der Gesellschafter mit Anteilen fuer § 3 Satzung."""
|
|
lines = []
|
|
for g in gesellschafter:
|
|
nr = g.get("anteil_nr", "?")
|
|
name = g.get("name", "")
|
|
nb = int(g.get("nennbetrag_eur") or 0)
|
|
lines.append(
|
|
f"{nr}. {name} übernimmt den Geschäftsanteil Nr. {nr} mit einem "
|
|
f"Nennbetrag von {nb:,} Euro.".replace(",", ".")
|
|
)
|
|
return "\n".join(lines)
|
|
|
|
|
|
def _gf_liste(gf: list[dict[str, Any]]) -> str:
|
|
"""Liste der Geschaeftsfuehrer fuer Bestellungsbeschluss / HRB-Anmeldung."""
|
|
lines = []
|
|
for g in gf:
|
|
line = f"- **{g.get('name', '')}**"
|
|
if g.get("geburtsdatum"):
|
|
line += f", geboren am {g['geburtsdatum']}"
|
|
if g.get("adresse"):
|
|
line += f", wohnhaft in {g['adresse']}"
|
|
if g.get("internal_role"):
|
|
line += f" — {g['internal_role']}"
|
|
lines.append(line)
|
|
return "\n".join(lines)
|
|
|
|
|
|
def _company_purpose_bullets(bullets: list[str]) -> str:
|
|
return "\n".join(bullets) if bullets else "a) Allgemeine geschäftliche Tätigkeit"
|
|
|
|
|
|
def _roles_description(gesellschafter: list[dict[str, Any]]) -> str:
|
|
"""Generiert Anlage-A Rollenbeschreibung pro Gesellschafter."""
|
|
lines = []
|
|
for idx, g in enumerate(gesellschafter):
|
|
name = g.get("name", "")
|
|
role = g.get("internal_role") or "Gesellschafter"
|
|
lines.append(f"({idx + 2}) **{role} — {name}**")
|
|
lines.append(f"Verantwortlich für die operative Leitung im Bereich {role}.\n")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def _einzahlungsaufstellung(gesellschafter: list[dict[str, Any]], quote_pct: int) -> str:
|
|
rows = []
|
|
for g in gesellschafter:
|
|
nb = int(g.get("nennbetrag_eur") or 0)
|
|
paid = int(nb * quote_pct / 100)
|
|
rows.append(f"- {g.get('name', '')}: {paid:,} EUR von {nb:,} EUR ({quote_pct}%)".replace(",", "."))
|
|
return "\n".join(rows)
|
|
|
|
|
|
def base_context(state: dict[str, Any]) -> dict[str, Any]:
|
|
"""Gemeinsamer Context fuer alle Dokumente."""
|
|
basics = state.get("basics", {})
|
|
capital = state.get("capital", {})
|
|
notar = state.get("notar", {})
|
|
gesellschafter = state.get("gesellschafter", [])
|
|
gf_list = [g for g in gesellschafter if g.get("is_geschaeftsfuehrer")]
|
|
sha = state.get("sha", {})
|
|
|
|
stammkapital = int(capital.get("stammkapital_eur") or 25000)
|
|
num_gf = len(gf_list)
|
|
num_gs = len(gesellschafter)
|
|
has_academic = any(g.get("has_academic_background") for g in gesellschafter)
|
|
|
|
ctx: dict[str, Any] = {
|
|
# Company
|
|
"COMPANY_NAME": basics.get("company_name", ""),
|
|
"COMPANY_LEGAL_FORM": basics.get("legal_form", "GmbH"),
|
|
"COMPANY_SEAT": basics.get("company_seat", ""),
|
|
"COMPANY_ADDRESS": basics.get("company_address", ""),
|
|
"COMPANY_PURPOSE_DESCRIPTION": basics.get("company_purpose_description", ""),
|
|
"COMPANY_PURPOSE_BULLETS": _company_purpose_bullets(basics.get("company_purpose_bullets", [])),
|
|
"COMPANY_PURPOSE_SHORT": basics.get("industry", "")[:120],
|
|
"BUSINESS_YEAR": basics.get("business_year", "Kalenderjahr"),
|
|
"FIRST_YEAR_END": "31. Dezember des Eintragungsjahres",
|
|
"PUBLICATION_VENUE": "Bundesanzeiger",
|
|
# Capital
|
|
"STAMMKAPITAL_EUR": f"{stammkapital:,}".replace(",", "."),
|
|
"STAMMKAPITAL_HALF_EUR": f"{stammkapital // 2:,}".replace(",", "."),
|
|
"EINLAGE_METHOD": capital.get("einlage_method", "Geld"),
|
|
"EINLAGE_QUOTE_INITIAL_PCT": capital.get("einlage_quote_initial_pct", 50),
|
|
"EINLAGE_QUOTE_REMAINING_PCT": 100 - int(capital.get("einlage_quote_initial_pct") or 50),
|
|
"EINLAGE_QUOTE_INITIAL_LESS_THAN_100": (capital.get("einlage_quote_initial_pct") or 50) < 100,
|
|
"EINZAHLUNGSAUFSTELLUNG": _einzahlungsaufstellung(gesellschafter, capital.get("einlage_quote_initial_pct") or 50),
|
|
"HAS_SACHEINLAGE": capital.get("has_sacheinlage", False),
|
|
"VERZUGSFRIST_TAGE": 30,
|
|
"EINZIEHUNG_MEHRHEIT_PCT": 75,
|
|
"VORKAUFSRECHT_TAGE": 14,
|
|
"EINBERUFUNGSFRIST_TAGE": 7,
|
|
"VOTING_UNIT_EUR": "1,00",
|
|
"ERBFALL_AUFGRIFFSFRIST_MONATE": 6,
|
|
"ERBFALL_MEHRHEIT_PCT": 75,
|
|
"AUFLOESUNG_MEHRHEIT_PCT": 75,
|
|
"GRUENDUNGSKOSTEN_MAX_EUR": f"{int(stammkapital / 10):,}".replace(",", "."),
|
|
# Gesellschafter
|
|
"PARTIES_LIST": _parties_list(gesellschafter),
|
|
"PARTIES_LIST_WITH_SHARES": _parties_list_with_shares(gesellschafter),
|
|
"GESELLSCHAFTER_TABELLE": _gs_table(gesellschafter, stammkapital),
|
|
"GESCHAEFTSFUEHRER_LISTE": _gf_liste(gf_list),
|
|
"GESELLSCHAFTER_LISTE": _gf_liste(gesellschafter),
|
|
# GF
|
|
"NUM_GF": num_gf,
|
|
"NUM_GF_TEXT": {1: "einen", 2: "zwei", 3: "drei", 4: "vier", 5: "fünf"}.get(num_gf, str(num_gf)),
|
|
"IS_SINGLE_GF": num_gf == 1,
|
|
"IS_MULTI_GF": num_gf > 1,
|
|
"NUM_GF_IS_2": num_gf == 2,
|
|
"NUM_GF_GT_2": num_gf > 2,
|
|
"IS_MULTI_GESELLSCHAFTER": num_gs > 1,
|
|
"IS_FOUNDER_GROUP": num_gs >= 2,
|
|
"VERTRETUNGSART": "Gesamtvertretung; bei nur einem Geschäftsführer Einzelvertretung",
|
|
# Notar
|
|
"NOTARY_NAME": notar.get("notary_name", ""),
|
|
"NOTARY_PLACE": notar.get("notary_place", ""),
|
|
"NOTARY_ADDRESS": notar.get("notary_address", ""),
|
|
"NOTARY_URNR": notar.get("urnr", "[wird beim Termin vergeben]"),
|
|
"NOTARIAL_DATE": notar.get("notarial_date", "[Notartermin folgt]"),
|
|
"NOTARY_BEGLAUBIGUNG_URNR": "[wird beim Termin vergeben]",
|
|
"NOTARIAL_LOCATION": notar.get("notary_place", ""),
|
|
"ANMELDUNG_TYP": "Ersteintragung gemäß § 7 GmbHG",
|
|
"ANMELDUNG_DATE": notar.get("notarial_date", "[Notartermin folgt]"),
|
|
"REGISTRY_COURT_ADDRESS": "[Adresse des zuständigen Registergerichts]",
|
|
"COMPANY_REGISTRY_COURT": basics.get("register_court") or "[zuständiges Amtsgericht]",
|
|
"REGISTER_COURT": basics.get("register_court") or "[zuständiges Amtsgericht]",
|
|
# Common
|
|
"DOCUMENT_VERSION": "1.0.0",
|
|
"EFFECTIVE_DATE": notar.get("notarial_date", "[Datum der Beurkundung]"),
|
|
"RESOLUTION_DATE": notar.get("notarial_date", "[Datum der Beurkundung]"),
|
|
"NEXT_REVIEW_DATE": "[+ 12 Monate]",
|
|
"SIGNATURES_BLOCK": "Unterschriften gemäß notarieller Beurkundung",
|
|
# SHA Flags
|
|
"HAS_SHA": sha.get("has_sha", True),
|
|
"HAS_GO_GF": True,
|
|
"HAS_ACADEMIC_FOUNDER": has_academic,
|
|
"HAS_RESEARCH_FOCUS": basics.get("has_research_focus", False),
|
|
"HAS_BEIRAT": sha.get("has_beirat", False),
|
|
"HAS_TEXAS_SHOOTOUT": sha.get("has_texas_shootout", False),
|
|
"HAS_CEO_DESIGNATION": sha.get("has_ceo_designation", False),
|
|
"CEO_NAME": sha.get("ceo_name", ""),
|
|
"HAS_HRB": bool(basics.get("hrb_number")),
|
|
"HRB_NUMBER": basics.get("hrb_number") or "[wird vergeben]",
|
|
"IS_UG": basics.get("legal_form") == "UG",
|
|
# GO-GF dynamische §-Numerierung
|
|
"P_INFO": 5,
|
|
"P_GESELLSCHAFTER": 4 if num_gf == 1 else 4,
|
|
"P_AUSSER": 5,
|
|
"P_ENT": 6,
|
|
"P_FIN": 7,
|
|
"P_PERS": 8,
|
|
"P_IK": 9,
|
|
"P_NEB": 10,
|
|
"P_DOC": 10,
|
|
"P_DOC_NEXT": 2,
|
|
"P_DOC_NEXT_2": 3,
|
|
"P_END": 11,
|
|
"LAST_PARA_4": 4 if num_gf == 2 else 5,
|
|
# SHA dynamische §-Numerierung (mit/ohne Beirat)
|
|
"P_NONCOMPETE": 16 if sha.get("has_beirat") else 15,
|
|
"P_CONFIDENTIAL": 17 if sha.get("has_beirat") else 16,
|
|
"P_TERM": 18 if sha.get("has_beirat") else 17,
|
|
"P_FINAL": 19 if sha.get("has_beirat") else 18,
|
|
"P_IP_PARA_6": 6 if has_academic else 3,
|
|
"P_IP_PARA_7": 7 if has_academic else 4,
|
|
"P_IP_PARA_8": 8 if has_academic else 5,
|
|
"P_DEADLOCK_FINAL": 5,
|
|
"P_DEADLOCK_LAST": 6,
|
|
"LAST_ROLE_PARA": len(gesellschafter) + 2,
|
|
"LAST_ROLE_PARA_PLUS_1": len(gesellschafter) + 3,
|
|
# Satzung dynamische §-Numerierung
|
|
"P_EINZIEHUNG": 7,
|
|
"P_VORKAUF": 8,
|
|
"P_TAGALONG": 9,
|
|
"P_DRAGALONG": 10,
|
|
"P_VERSAMMLUNG": 11,
|
|
"P_JA": 12,
|
|
"P_ERGEBNIS": 13,
|
|
"P_AUFGRIFF": 14,
|
|
"P_ABTRETUNG": 15,
|
|
"P_ERBE": 16,
|
|
"P_AUFL": 17,
|
|
"P_SCHLUSS": 18,
|
|
# SHA Eskalation und sonstige Schwellenwerte
|
|
"ESKALATION_TAGE_INTERN": 5,
|
|
"ESKALATION_TAGE_GESELLSCHAFTER": 14,
|
|
"ERHEBLICH_EUR": "10.000",
|
|
"DEADLOCK_FRIST_TAGE": 30,
|
|
"MEDIATION_INIT_TAGE": 7,
|
|
"MEDIATOR_FRIST_TAGE": 5,
|
|
"MEDIATION_MAX_TAGE": 30,
|
|
"SHOOTOUT_FRIST_TAGE": 14,
|
|
"SHOOTOUT_ABWICKLUNG_TAGE": 60,
|
|
"ESKALATION_TAGE": 30,
|
|
"JURISDICTION_LOCATION": basics.get("company_seat", "[Sitz]"),
|
|
"PARA_181_DETAILS": "soweit Geschäftsführer von den Beschränkungen befreit",
|
|
"ARCHIV_VERANTWORTLICH": "Geschäftsführung",
|
|
"DOKUMENTATIONS_SYSTEM": "elektronischen Dokumentenmanagement",
|
|
"ARCHIVIERUNG_JAHRE": 10,
|
|
"REVIEW_VERANTWORTLICH": "Geschäftsführung",
|
|
"MEETING_OPERATIVE_FREQ": "wöchentliche",
|
|
"MEETING_STRATEGIE_FREQ": "monatliche",
|
|
"SCHWELLE_EINZEL_EUR": "10.000",
|
|
"SCHWELLE_EINZEL_EUR_PLUS_1": "10.001",
|
|
"SCHWELLE_GEMEINSAM_EUR": "50.000",
|
|
"SCHWELLE_GESELLSCHAFTER_EUR": "50.000",
|
|
"BUDGET_ABWEICHUNG_PCT": 10,
|
|
"VERTRAG_LAUFZEIT_MONATE": 24,
|
|
"VERTRAG_WERT_EUR": "50.000",
|
|
"LIQUIDITAET_MIN_MONATE": 3,
|
|
"FORECAST_HORIZON_MONTHS": 12,
|
|
"SCHLUESSELPERSON_GEHALT_EUR": "80.000",
|
|
"NEBENTAETIGKEIT_MAX_STUNDEN": 8,
|
|
# SHA-Spezifika
|
|
"VESTING_START_EVENT": "Eintragung der Gesellschaft im Handelsregister",
|
|
"VESTING_MONTHS": sha.get("vesting_months", 48),
|
|
"CLIFF_MONTHS": sha.get("cliff_months", 12),
|
|
"ACCELERATION_THRESHOLD_PCT": 50,
|
|
"ACCELERATION_PCT": 100,
|
|
"BAD_LEAVER_UNVESTED_PCT": 20,
|
|
"FMV_AGREEMENT_DAYS": 14,
|
|
"ABFINDUNG_RATEN_MAX": 24,
|
|
"NON_SOLICIT_MONTHS": 12,
|
|
"VORKAUFSRECHT_TAGE": 14,
|
|
"TAG_ALONG_THRESHOLD_PCT": sha.get("tag_along_threshold_pct", 20),
|
|
"TAG_ALONG_FRIST_TAGE": 14,
|
|
"DRAG_ALONG_THRESHOLD_PCT": sha.get("drag_along_threshold_pct", 75),
|
|
"RESERVED_MATTERS_MAJORITY_PCT": sha.get("reserved_matters_majority_pct", 75),
|
|
"ASSET_THRESHOLD_EUR": "50.000",
|
|
"ESOP_POOL_PCT": sha.get("esop_pool_pct", 0),
|
|
"INVESTOR_INFO_THRESHOLD_EUR": "50.000",
|
|
"ANNUAL_REPORT_MONTHS": 6,
|
|
"BEIRAT_MAX_MITGLIEDER": 5,
|
|
"BEIRAT_FREQ": "vierteljährlich",
|
|
"PASSIVE_INVEST_PCT": 5,
|
|
"POST_EXIT_GOOD_MONTHS": 12,
|
|
"POST_EXIT_BAD_MONTHS": 24,
|
|
"ROLES_DESCRIPTION": _roles_description(gesellschafter),
|
|
"SIGNATURE_DATE": notar.get("notarial_date", "[Datum]"),
|
|
# Gesellschafterliste
|
|
"LIST_DATE": notar.get("notarial_date", "[Datum]"),
|
|
"LIST_AUTHOR": gf_list[0].get("name", "") if gf_list else "",
|
|
"LIST_AUTHOR_ROLE": "Geschäftsführer",
|
|
"LIST_REASON": "Erstaufstellung gemäß § 40 GmbHG",
|
|
"SIGNATORY_NAME": gf_list[0].get("name", "") if gf_list else "",
|
|
"SIGNATORY_ROLE": "Geschäftsführer",
|
|
"SIGNATORY_2_NAME": gf_list[1].get("name", "") if len(gf_list) > 1 else "",
|
|
"SIGNATORY_2_ROLE": "Geschäftsführer",
|
|
"MULTI_SIGNATORY": len(gf_list) > 1,
|
|
# Bestellungsbeschluss
|
|
"MEETING_LOCATION": notar.get("notary_place", "[Notarsitz]"),
|
|
"RESOLUTION_FORM": "notariell beurkundet",
|
|
"ANWESENHEITSQUOTE_PCT": 100,
|
|
"IS_EINSTIMMIG": True,
|
|
"BESCHLUSS_MEHRHEIT_PCT": 100,
|
|
"IS_PRESENCE_MEETING": True,
|
|
"IS_SINGLE_APPOINTMENT": num_gf == 1,
|
|
"IS_MULTI_APPOINTMENT": num_gf > 1,
|
|
"IS_FIRST_APPOINTMENT": True,
|
|
"IS_PLURAL_GF": num_gf > 1,
|
|
"GF_NAME": gf_list[0].get("name", "") if gf_list else "",
|
|
"GF_BIRTHDATE": gf_list[0].get("geburtsdatum", "") if gf_list else "",
|
|
"GF_BIRTHDATE_PLACE": "[Geburtsort]",
|
|
"GF_ADDRESS": gf_list[0].get("adresse", "") if gf_list else "",
|
|
"GF_VERTRETUNG": "einzelvertretungsberechtigt" if num_gf == 1 else "gemeinsam mit einem weiteren Geschäftsführer vertretungsberechtigt",
|
|
"GF_PARA_181_RELEASE": True,
|
|
"GF_LISTE_MIT_VERTRETUNGSART": "\n".join(
|
|
f"- {g.get('name', '')}, geb. {g.get('geburtsdatum', '')}, wohnhaft in {g.get('adresse', '')}, "
|
|
f"vertretungsberechtigt {'allein' if num_gf == 1 else 'gemeinsam'}; § 181 BGB-Befreiung erteilt"
|
|
for g in gf_list
|
|
),
|
|
"HAS_RESSORT_ZUWEISUNG": True,
|
|
"HAS_DIENSTVERTRAG": True,
|
|
"SIGNATURES_GESELLSCHAFTER": "\n".join(
|
|
f"___________________________\n{g.get('name', '')}"
|
|
for g in gesellschafter
|
|
),
|
|
"HAS_VERSICHERUNG_BESTELLT": True,
|
|
"BELEHRUNG_DURCH": "den beurkundenden Notar",
|
|
"HAS_DELAYED_START": False,
|
|
# HRB-Anmeldung
|
|
"VERTRETUNGSREGELUNG": (
|
|
"Die Gesellschaft wird durch einen Geschäftsführer allein vertreten."
|
|
if num_gf == 1 else
|
|
"Die Gesellschaft wird durch zwei Geschäftsführer gemeinsam vertreten. "
|
|
"Bei nur einem bestellten Geschäftsführer Einzelvertretung."
|
|
),
|
|
"GF_SIGNATURES_BEGLAUBIGUNG": "\n".join(
|
|
f"___________________________\n{g.get('name', '')}, Geschäftsführer"
|
|
for g in gf_list
|
|
),
|
|
"HAS_EMPFANGSBERECHTIGTER": False,
|
|
"EMPFANGSBERECHTIGTER_NAME": "",
|
|
"EMPFANGSBERECHTIGTER_ADDRESS": "",
|
|
"HAS_GENEHMIGUNG": False,
|
|
"GENEHMIGUNG_DETAILS": "",
|
|
"NEXT_DOC_NUMBER": 6,
|
|
# GF-Dienstvertrag (Defaults für alle GFs, einzelne Felder per Contract überschreiben)
|
|
"COMPANY_REPRESENTATIVE": "die Gesellschafterversammlung",
|
|
"APPOINTMENT_DATE": notar.get("notarial_date", "[Datum]"),
|
|
"GF_INTERNAL_TITLE": gf_list[0].get("internal_role", "Geschäftsführer") if gf_list else "Geschäftsführer",
|
|
"CONTRACT_START_DATE": notar.get("notarial_date", "[Datum]"),
|
|
"HAS_PARA_181_RELEASE": True,
|
|
"PARA_181_RELEASE_DATE": notar.get("notarial_date", "[Datum]"),
|
|
"HAS_BONUS": False, "HAS_TANTIEME": False, "HAS_COMPANY_CAR": False, "HAS_BAV": False,
|
|
"HAS_HINTERBLIEBENEN_VERSORGUNG": False, "HAS_KOPPLUNG_BESTELLUNG_VERTRAG": False,
|
|
"HAS_NONCOMPETE_COMPENSATION": False,
|
|
"POST_CONTRACT_NONCOMPETE_MONTHS": 12,
|
|
"GROSS_ANNUAL_SALARY_EUR": "84.000",
|
|
"COMPANY_CAR_CLASS": "",
|
|
"BAV_EMPLOYER_PCT": 0,
|
|
"SV_STATUS": "sozialversicherungsfrei",
|
|
"VACATION_DAYS": 30,
|
|
"KRANKHEIT_FORTZAHLUNG_WOCHEN": 6,
|
|
"AU_BESCHEINIGUNG_TAG": 4,
|
|
"HINTERBLIEBENEN_VERSORGUNG_MONATE": 6,
|
|
"DO_INSURANCE_EUR": "5.000.000",
|
|
"KUENDIGUNGSFRIST_GESELLSCHAFT_MONATE": 6,
|
|
"KUENDIGUNGSFRIST_GF_MONATE": 3,
|
|
"ANNEX_LIST": "- Anlage 1: Bonusplan (sofern vereinbart)\n- Anlage 2: D&O-Versicherungspolice",
|
|
# IP-Assignment
|
|
"ASSIGNOR_NAME": gf_list[0].get("name", "") if gf_list else "",
|
|
"ASSIGNOR_BIRTHDATE": gf_list[0].get("geburtsdatum", "") if gf_list else "",
|
|
"ASSIGNOR_ADDRESS": gf_list[0].get("adresse", "") if gf_list else "",
|
|
"ASSIGNOR_ROLE": gf_list[0].get("internal_role", "Gründer und Geschäftsführer") if gf_list else "Gründer",
|
|
"AGREEMENT_DATE": notar.get("notarial_date", "[Datum]"),
|
|
"HAS_BAR_VERGUETUNG": False,
|
|
"HAS_SHARES_AS_COMPENSATION": True,
|
|
"HAS_NO_VERGUETUNG": False,
|
|
"IP_VERGUETUNG_EUR": 0,
|
|
"ZAHLUNGSFRIST_TAGE": 30,
|
|
"GUARANTEE_VERJAEHRUNG_JAHRE": 3,
|
|
"HAS_ACADEMIC_BACKGROUND": has_academic,
|
|
"SIGNATURE_LOCATION": basics.get("company_seat", "[Sitz]"),
|
|
"IP_LIST_DETAILS": "- Software-Architektur und Quellcode (bestehend zum Zeitpunkt der Gründung)\n- Konzepte, Designs, Datenbankstrukturen\n- Marken, Logos, Domainnamen",
|
|
"IP_EXCEPTIONS_DETAILS": "Keine Ausnahmen bekannt.",
|
|
}
|
|
# Ressort-Variablen aus GF-Liste ableiten (1 Ressort pro GF)
|
|
ressort_defaults = [
|
|
("Operative & Kommerzielle Leitung", "Finanzen, HR, Vertrieb, Business Development, operative Steuerung"),
|
|
("Technik & Engineering", "Softwareentwicklung, Architektur, Infrastruktur, Sicherheit, technische Roadmap"),
|
|
("Research & Partnerships", "Forschungskooperationen, Drittmittel, wissenschaftliche Methodik"),
|
|
]
|
|
for idx, gf in enumerate(gf_list[:3]):
|
|
n = idx + 1
|
|
default_name, default_aufgaben = ressort_defaults[idx] if idx < 3 else ("Allgemeine Leitung", "Sonstige Aufgaben")
|
|
ctx[f"RESSORT_{n}_NAME"] = gf.get("internal_role") or default_name
|
|
ctx[f"RESSORT_{n}_GF"] = gf.get("name", "")
|
|
ctx[f"RESSORT_{n}_AUFGABEN"] = f"- {default_aufgaben}"
|
|
ctx["HAS_RESSORT_3"] = len(gf_list) >= 3
|
|
return ctx
|