feat(pitch-deck): add admin migration endpoint for finanzplan tables

POST /api/admin/migrate creates all fp_* tables on production DB.
Admin-only, creates tables with IF NOT EXISTS for safe re-runs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-15 19:06:32 +02:00
parent a9b71b9d23
commit 4d7836540a

View File

@@ -0,0 +1,128 @@
import { NextRequest, NextResponse } from 'next/server'
import { requireAdmin } from '@/lib/admin-auth'
import pool from '@/lib/db'
export async function POST(request: NextRequest) {
const guard = await requireAdmin(request)
if (guard.kind === 'response') return guard.response
const results: string[] = []
// Finanzplan tables — the ones missing on production
const statements = [
`CREATE TABLE IF NOT EXISTS fp_scenarios (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL DEFAULT 'Base Case',
description TEXT,
is_default BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
`INSERT INTO fp_scenarios (name, description, is_default)
SELECT 'Base Case', 'Basisdaten aus Excel-Import', true
WHERE NOT EXISTS (SELECT 1 FROM fp_scenarios WHERE is_default = true)`,
`CREATE TABLE IF NOT EXISTS fp_kunden (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
segment_name TEXT NOT NULL, segment_index INT NOT NULL, row_label TEXT NOT NULL, row_index INT NOT NULL,
percentage NUMERIC(5,3), formula_type TEXT, is_editable BOOLEAN DEFAULT false,
values JSONB NOT NULL DEFAULT '{}', excel_row INT, sort_order INT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
`CREATE TABLE IF NOT EXISTS fp_kunden_summary (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
row_label TEXT NOT NULL, row_index INT NOT NULL, values JSONB NOT NULL DEFAULT '{}',
excel_row INT, sort_order INT NOT NULL
)`,
`CREATE TABLE IF NOT EXISTS fp_umsatzerloese (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
section TEXT NOT NULL, row_label TEXT NOT NULL, row_index INT NOT NULL,
is_editable BOOLEAN DEFAULT false, values JSONB NOT NULL DEFAULT '{}',
excel_row INT, sort_order INT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
`CREATE TABLE IF NOT EXISTS fp_materialaufwand (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
section TEXT NOT NULL, row_label TEXT NOT NULL, row_index INT NOT NULL,
is_editable BOOLEAN DEFAULT false, values JSONB NOT NULL DEFAULT '{}',
excel_row INT, sort_order INT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
`CREATE TABLE IF NOT EXISTS fp_personalkosten (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
person_name TEXT NOT NULL, person_nr TEXT, position TEXT,
start_date DATE, end_date DATE, brutto_monthly NUMERIC(10,2),
annual_raise_pct NUMERIC(5,2) DEFAULT 3.0, ag_sozial_pct NUMERIC(5,2) DEFAULT 20.425,
is_editable BOOLEAN DEFAULT true,
values_brutto JSONB NOT NULL DEFAULT '{}', values_sozial JSONB NOT NULL DEFAULT '{}',
values_total JSONB NOT NULL DEFAULT '{}',
excel_row INT, sort_order INT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
`CREATE TABLE IF NOT EXISTS fp_betriebliche_aufwendungen (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
category TEXT NOT NULL, row_label TEXT NOT NULL, row_index INT NOT NULL,
is_editable BOOLEAN DEFAULT true, is_sum_row BOOLEAN DEFAULT false, formula_desc TEXT,
values JSONB NOT NULL DEFAULT '{}', excel_row INT, sort_order INT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
`CREATE TABLE IF NOT EXISTS fp_investitionen (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
item_name TEXT NOT NULL, category TEXT, purchase_amount NUMERIC(12,2) NOT NULL,
purchase_date DATE, afa_years INT, afa_end_date DATE, is_editable BOOLEAN DEFAULT true,
values_invest JSONB NOT NULL DEFAULT '{}', values_afa JSONB NOT NULL DEFAULT '{}',
excel_row INT, sort_order INT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
`CREATE TABLE IF NOT EXISTS fp_sonst_ertraege (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
category TEXT NOT NULL, row_label TEXT, row_index INT NOT NULL,
is_editable BOOLEAN DEFAULT true, is_sum_row BOOLEAN DEFAULT false,
values JSONB NOT NULL DEFAULT '{}', excel_row INT, sort_order INT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
`CREATE TABLE IF NOT EXISTS fp_liquiditaet (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
row_label TEXT NOT NULL, row_type TEXT NOT NULL,
is_editable BOOLEAN DEFAULT false, formula_desc TEXT,
values JSONB NOT NULL DEFAULT '{}', excel_row INT, sort_order INT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
`CREATE TABLE IF NOT EXISTS fp_guv (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
row_label TEXT NOT NULL, row_index INT NOT NULL,
is_sum_row BOOLEAN DEFAULT false, formula_desc TEXT,
values JSONB NOT NULL DEFAULT '{}', excel_row INT, sort_order INT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW()
)`,
`CREATE TABLE IF NOT EXISTS fp_cell_overrides (
id SERIAL PRIMARY KEY, scenario_id UUID REFERENCES fp_scenarios(id) ON DELETE CASCADE,
sheet_name TEXT NOT NULL, row_id INT NOT NULL, month_key TEXT NOT NULL,
override_value NUMERIC, created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(scenario_id, sheet_name, row_id, month_key)
)`,
`CREATE INDEX IF NOT EXISTS idx_fp_kunden_scenario ON fp_kunden(scenario_id)`,
`CREATE INDEX IF NOT EXISTS idx_fp_kunden_summary_scenario ON fp_kunden_summary(scenario_id)`,
`CREATE INDEX IF NOT EXISTS idx_fp_umsatz_scenario ON fp_umsatzerloese(scenario_id)`,
`CREATE INDEX IF NOT EXISTS idx_fp_material_scenario ON fp_materialaufwand(scenario_id)`,
`CREATE INDEX IF NOT EXISTS idx_fp_personal_scenario ON fp_personalkosten(scenario_id)`,
`CREATE INDEX IF NOT EXISTS idx_fp_betrieb_scenario ON fp_betriebliche_aufwendungen(scenario_id)`,
`CREATE INDEX IF NOT EXISTS idx_fp_invest_scenario ON fp_investitionen(scenario_id)`,
`CREATE INDEX IF NOT EXISTS idx_fp_sonst_scenario ON fp_sonst_ertraege(scenario_id)`,
`CREATE INDEX IF NOT EXISTS idx_fp_liquid_scenario ON fp_liquiditaet(scenario_id)`,
`CREATE INDEX IF NOT EXISTS idx_fp_guv_scenario ON fp_guv(scenario_id)`,
`CREATE INDEX IF NOT EXISTS idx_fp_overrides_lookup ON fp_cell_overrides(scenario_id, sheet_name, row_id)`,
]
for (const sql of statements) {
try {
await pool.query(sql)
const label = sql.substring(0, 60).replace(/\s+/g, ' ')
results.push(`OK: ${label}...`)
} catch (err) {
const msg = err instanceof Error ? err.message : String(err)
results.push(`ERROR: ${msg}`)
}
}
return NextResponse.json({ success: true, results })
}