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