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:
128
pitch-deck/app/api/admin/migrate/route.ts
Normal file
128
pitch-deck/app/api/admin/migrate/route.ts
Normal 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 })
|
||||
}
|
||||
Reference in New Issue
Block a user