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>
221 lines
8.9 KiB
TypeScript
221 lines
8.9 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import type { FoundingWizardState } from '@/lib/sdk/founding/types'
|
|
|
|
interface Props {
|
|
state: FoundingWizardState
|
|
update: <K extends keyof FoundingWizardState>(k: K, v: FoundingWizardState[K]) => void
|
|
}
|
|
|
|
export function StepBasics({ state, update }: Props) {
|
|
const b = state.basics
|
|
const [prefillStatus, setPrefillStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle')
|
|
|
|
async function prefillFromCompanyProfile() {
|
|
setPrefillStatus('loading')
|
|
try {
|
|
const res = await fetch('/api/sdk/v1/company-profile', { cache: 'no-store' })
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
const payload = await res.json()
|
|
const p = payload?.profile ?? payload
|
|
if (!p || typeof p !== 'object') throw new Error('leeres Profil')
|
|
const industries = Array.isArray(p.industry) ? p.industry.filter(Boolean) : []
|
|
const industry = industries.length > 0
|
|
? industries.join(', ')
|
|
: (p.industryOther || b.industry)
|
|
const address = [p.headquartersStreet, [p.headquartersZip, p.headquartersCity].filter(Boolean).join(' ')]
|
|
.filter(Boolean).join(', ') || b.company_address
|
|
const seat = p.headquartersCity || b.company_seat
|
|
// Purpose ableiten aus offerings/businessModel — Fallback wenn nichts da
|
|
const purposeBits: string[] = []
|
|
if (p.businessModel) purposeBits.push(`Geschäftsmodell: ${p.businessModel}`)
|
|
if (Array.isArray(p.offerings) && p.offerings.length > 0)
|
|
purposeBits.push(`Leistungen: ${p.offerings.join(', ')}`)
|
|
const purpose = purposeBits.length > 0
|
|
? purposeBits.join('; ')
|
|
: b.company_purpose_description
|
|
update('basics', {
|
|
...b,
|
|
company_name: p.companyName || b.company_name,
|
|
legal_form: (p.legalForm === 'UG' ? 'UG' : (p.legalForm === 'GmbH' ? 'GmbH' : b.legal_form)),
|
|
company_seat: seat,
|
|
company_address: address,
|
|
industry,
|
|
company_purpose_description: b.company_purpose_description.trim() === '' ? purpose : b.company_purpose_description,
|
|
})
|
|
setPrefillStatus('success')
|
|
} catch (err) {
|
|
console.error('[founding-wizard] prefill failed', err)
|
|
setPrefillStatus('error')
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<p className="text-sm text-gray-600">
|
|
Stammdaten der Gesellschaft. Pflicht für Satzung, HRB-Anmeldung und SHA.
|
|
</p>
|
|
<button
|
|
type="button"
|
|
onClick={prefillFromCompanyProfile}
|
|
disabled={prefillStatus === 'loading'}
|
|
className="px-3 py-1.5 text-sm rounded-lg border border-blue-300 bg-blue-50 hover:bg-blue-100 disabled:opacity-50"
|
|
>
|
|
{prefillStatus === 'loading' ? 'Lade…' : 'Aus Unternehmensprofil vorbefüllen'}
|
|
</button>
|
|
</div>
|
|
{prefillStatus === 'success' && (
|
|
<div className="text-xs text-green-700 bg-green-50 border border-green-200 rounded px-2 py-1">
|
|
Daten aus Unternehmensprofil übernommen. Bitte prüfen und ergänzen.
|
|
</div>
|
|
)}
|
|
{prefillStatus === 'error' && (
|
|
<div className="text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded px-2 py-1">
|
|
Konnte Unternehmensprofil nicht laden — bitte Felder manuell ausfüllen.
|
|
</div>
|
|
)}
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Firmenname</label>
|
|
<input
|
|
data-testid="company-name"
|
|
type="text"
|
|
value={b.company_name}
|
|
onChange={e => update('basics', { ...b, company_name: e.target.value })}
|
|
placeholder="Breakpilot GmbH"
|
|
className="w-full px-3 py-2 border rounded-lg"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Rechtsform</label>
|
|
<select
|
|
data-testid="legal-form"
|
|
value={b.legal_form}
|
|
onChange={e => update('basics', { ...b, legal_form: e.target.value as 'GmbH' | 'UG' })}
|
|
className="w-full px-3 py-2 border rounded-lg"
|
|
>
|
|
<option value="GmbH">GmbH</option>
|
|
<option value="UG">UG (haftungsbeschränkt)</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Sitz (Stadt)</label>
|
|
<input
|
|
data-testid="company-seat"
|
|
type="text"
|
|
value={b.company_seat}
|
|
onChange={e => update('basics', { ...b, company_seat: e.target.value })}
|
|
placeholder="z.B. Stuttgart"
|
|
className="w-full px-3 py-2 border rounded-lg"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Adresse</label>
|
|
<input
|
|
data-testid="company-address"
|
|
type="text"
|
|
value={b.company_address}
|
|
onChange={e => update('basics', { ...b, company_address: e.target.value })}
|
|
placeholder="Straße, PLZ Ort"
|
|
className="w-full px-3 py-2 border rounded-lg"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Branche</label>
|
|
<input
|
|
data-testid="industry"
|
|
type="text"
|
|
value={b.industry}
|
|
onChange={e => update('basics', { ...b, industry: e.target.value })}
|
|
placeholder="z.B. SaaS, Beratung, Handwerk"
|
|
className="w-full px-3 py-2 border rounded-lg"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Geschäftsjahr</label>
|
|
<input
|
|
data-testid="business-year"
|
|
type="text"
|
|
value={b.business_year}
|
|
onChange={e => update('basics', { ...b, business_year: e.target.value })}
|
|
className="w-full px-3 py-2 border rounded-lg"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Registergericht
|
|
</label>
|
|
<input
|
|
data-testid="register-court"
|
|
type="text"
|
|
value={b.register_court || ''}
|
|
onChange={e => update('basics', { ...b, register_court: e.target.value })}
|
|
placeholder="z.B. Amtsgericht Stuttgart"
|
|
className="w-full px-3 py-2 border rounded-lg"
|
|
/>
|
|
<p className="text-xs text-gray-500 mt-1">
|
|
Zuständiges Amtsgericht für HRB-Eintragung
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
HRB-Nummer <span className="text-gray-400">(optional)</span>
|
|
</label>
|
|
<input
|
|
data-testid="hrb-number"
|
|
type="text"
|
|
value={b.hrb_number || ''}
|
|
onChange={e => update('basics', { ...b, hrb_number: e.target.value })}
|
|
placeholder="z.B. HRB 12345 (leer falls noch nicht eingetragen)"
|
|
className="w-full px-3 py-2 border rounded-lg"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Unternehmensgegenstand (Volltext für § 2 Satzung)
|
|
</label>
|
|
<textarea
|
|
data-testid="company-purpose"
|
|
value={b.company_purpose_description}
|
|
onChange={e => update('basics', { ...b, company_purpose_description: e.target.value })}
|
|
rows={4}
|
|
placeholder="z.B. die Entwicklung, Bereitstellung, der Betrieb und der Vertrieb von Softwarelösungen, Plattformen und IT-Dienstleistungen im Bereich der Künstlichen Intelligenz"
|
|
className="w-full px-3 py-2 border rounded-lg"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Detaillierte Tätigkeitsbereiche (eine Zeile pro Bullet)
|
|
</label>
|
|
<textarea
|
|
data-testid="company-purpose-bullets"
|
|
value={b.company_purpose_bullets.join('\n')}
|
|
onChange={e => update('basics', { ...b, company_purpose_bullets: e.target.value.split('\n').filter(Boolean) })}
|
|
rows={5}
|
|
placeholder={'a) Entwicklung von Software\nb) Beratung im Bereich...\nc) ...'}
|
|
className="w-full px-3 py-2 border rounded-lg font-mono text-sm"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2">
|
|
<input
|
|
type="checkbox"
|
|
id="research_focus"
|
|
data-testid="research-focus"
|
|
checked={b.has_research_focus}
|
|
onChange={e => update('basics', { ...b, has_research_focus: e.target.checked })}
|
|
/>
|
|
<label htmlFor="research_focus" className="text-sm text-gray-700">
|
|
Forschungsfokus (aktiviert F&E-Klauseln in SHA und GO-GF)
|
|
</label>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|