feat: Finanzplan Phase 1-4 — DB + Engine + API + Spreadsheet-UI

Phase 1: DB-Schema (12 fp_* Tabellen) + Excel-Import (332 Zeilen importiert)
Phase 2: Compute Engine (Personal, Invest, Umsatz, Material, Betrieblich, Liquiditaet, GuV)
Phase 3: API (/api/finanzplan/ — GET sheets, PUT cells, POST compute)
Phase 4: Spreadsheet-UI (FinanzplanSlide als Annex mit Tab-Leiste, editierbarem Grid, Jahres-Navigation)

Zusaetzlich:
- Gruendungsdatum verschoben: Feb→Aug 2026 (DB + Personalkosten)
- Neue Preisstaffel: Startup/<10 MA ab 3.600 EUR/Jahr (14-Tage-Test, Kreditkarte)
- Competition-Slide: Pricing-Tiers aktualisiert

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-26 19:26:46 +01:00
parent f514667ef9
commit a58cd16f01
16 changed files with 4589 additions and 5 deletions

View File

@@ -0,0 +1,583 @@
#!/usr/bin/env python3
"""
Import BreakPilot Finanzplan Excel into PostgreSQL fp_* tables.
Usage: python3 scripts/import-finanzplan.py <path-to-xlsm>
Requires: pip3 install openpyxl psycopg2-binary
"""
import sys
import json
import os
from datetime import date, datetime
import openpyxl
import psycopg2
from psycopg2.extras import Json
# --- Config ---
DB_URL = os.environ.get('DATABASE_URL', 'postgresql://breakpilot:breakpilot@localhost:5432/breakpilot_db')
MONTHS = 60 # Jan 2026 Dec 2030
# Excel columns: D=4 (month 1) through BQ=69 (month 60, approx)
# Actually: D=m1(Jan2026), E=m2(Feb2026), ..., O=m12(Dec2026),
# Q=m13(Jan2027), ..., AB=m24(Dec2027), etc.
# Year-column (C, P, AC, AP, BC) = annual sums, skip those
# Monthly columns per year: D-O (12), Q-AB (12), AD-AO (12), AQ-BB (12), BD-BO (12)
# Map: month_index (1-60) -> Excel column index (1-based)
def build_month_columns():
"""Build mapping from month 1-60 to Excel column index."""
cols = []
# Year 1 (2026): cols D(4) - O(15) = 12 months
for c in range(4, 16):
cols.append(c)
# Year 2 (2027): cols Q(17) - AB(28) = 12 months
for c in range(17, 29):
cols.append(c)
# Year 3 (2028): cols AD(30) - AO(41) = 12 months
for c in range(30, 42):
cols.append(c)
# Year 4 (2029): cols AQ(43) - BB(54) = 12 months
for c in range(43, 55):
cols.append(c)
# Year 5 (2030): cols BD(56) - BO(67) = 12 months
for c in range(56, 68):
cols.append(c)
return cols
MONTH_COLS = build_month_columns()
def read_monthly_values(ws, row, data_only=True):
"""Read 60 monthly values from an Excel row."""
values = {}
for m_idx, col in enumerate(MONTH_COLS):
v = ws.cell(row, col).value
if v is not None and v != '' and not isinstance(v, str):
try:
values[f'm{m_idx+1}'] = round(float(v), 2)
except (ValueError, TypeError):
values[f'm{m_idx+1}'] = 0
else:
values[f'm{m_idx+1}'] = 0
return values
def safe_str(v):
if v is None:
return ''
return str(v).strip()
def safe_float(v, default=0):
if v is None:
return default
try:
return float(v)
except (ValueError, TypeError):
return default
def safe_date(v):
if v is None:
return None
if isinstance(v, datetime):
return v.date()
if isinstance(v, date):
return v
return None
def import_personalkosten(cur, ws, scenario_id):
"""Import Personalkosten sheet (rows 10-29 = 20 positions)."""
print(" Importing Personalkosten...")
count = 0
for i in range(20):
row = 10 + i
name = safe_str(ws.cell(row, 1).value)
nr = safe_str(ws.cell(row, 2).value)
position = safe_str(ws.cell(row, 3).value)
start = safe_date(ws.cell(row, 4).value)
end = safe_date(ws.cell(row, 5).value)
brutto = safe_float(ws.cell(row, 7).value)
raise_pct = safe_float(ws.cell(row, 8).value, 3.0)
if not name and not nr:
continue
# Read computed monthly totals from the "Personalaufwand" section
# The actual monthly values are in a different row range (rows 32-51 for brutto, 56-75 for sozial)
# But we store the inputs and let the engine compute
values_total = {} # will be computed by engine
cur.execute("""
INSERT INTO fp_personalkosten
(scenario_id, person_name, person_nr, position, start_date, end_date,
brutto_monthly, annual_raise_pct, is_editable,
values_brutto, values_sozial, values_total, excel_row, sort_order)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, true, %s, %s, %s, %s, %s)
""", (scenario_id, name, nr, position, start, end,
brutto, raise_pct, Json({}), Json({}), Json({}), row, i + 1))
count += 1
print(f" -> {count} Positionen importiert")
def import_betriebliche_aufwendungen(cur, ws, scenario_id):
"""Import Betriebliche Aufwendungen (rows 3-47)."""
print(" Importing Betriebliche Aufwendungen...")
# Define the structure based on Excel analysis
rows_config = [
(3, 'personal', 'Personalkosten', False, True, '=Personalkosten total'),
(4, 'raumkosten', 'Raumkosten', True, False, None),
(5, 'steuern', 'Betriebliche Steuern', False, True, '=SUM(6,7)'),
(6, 'steuern', 'Gewerbesteuer', True, False, None),
(7, 'steuern', 'KFZ-Steuern', True, False, None),
(8, 'versicherungen', 'Versich./Beitraege', False, True, '=SUM(9:18)'),
(9, 'versicherungen', 'IHK', True, False, None),
(10, 'versicherungen', 'Rundfunkbeitrag', True, False, None),
(11, 'versicherungen', 'Berufsgenossenschaft', True, False, None),
(12, 'versicherungen', 'Bundesanzeiger/Transparenzregister', True, False, None),
(13, 'versicherungen', 'D&O-Versicherung', True, False, None),
(14, 'versicherungen', 'E&O-Versicherung', True, False, None),
(15, 'versicherungen', 'Produkthaftpflicht', True, False, None),
(16, 'versicherungen', 'Cyber-Versicherung', True, False, None),
(17, 'versicherungen', 'Rechtsschutzversicherung', True, False, None),
(18, 'versicherungen', 'KFZ-Versicherung', True, False, None),
(19, 'besondere', 'Besondere Kosten', False, True, '=SUM(20:22)'),
(20, 'besondere', 'Schutzrechte/Lizenzkosten', True, False, None),
(21, 'besondere', 'Marketing Videos', True, False, None),
(22, 'besondere', 'Fort-/Weiterbildungskosten', True, False, None),
(23, 'fahrzeug', 'Fahrzeugkosten', True, False, None),
(24, 'marketing', 'Werbe-/Reisekosten', False, True, '=SUM(25:30)'),
(25, 'marketing', 'Reisekosten', True, False, None),
(26, 'marketing', 'Teilnahme an Messen', True, False, None),
(27, 'marketing', 'Allgemeine Marketingkosten', True, False, None),
(28, 'marketing', 'Marketing-Agentur', True, False, None),
(29, 'marketing', 'Editorial Content', True, False, None),
(30, 'marketing', 'Bewirtungskosten', True, False, None),
(31, 'warenabgabe', 'Kosten Warenabgabe', True, False, None),
(32, 'abschreibungen', 'Abschreibungen', False, False, '=Investitionen AfA'),
(33, 'reparatur', 'Reparatur/Instandh.', True, False, None),
(34, 'sonstige', 'Sonstige Kosten', False, True, '=SUM(35:45)'),
(35, 'sonstige', 'Telefon', True, False, None),
(36, 'sonstige', 'Bankgebuehren', True, False, None),
(37, 'sonstige', 'Buchfuehrung', True, False, None),
(38, 'sonstige', 'Jahresabschluss', True, False, None),
(39, 'sonstige', 'Rechts-/Beratungskosten', True, False, None),
(40, 'sonstige', 'Werkzeuge/Kleingeraete', True, False, None),
(41, 'sonstige', 'Serverkosten (Cloud)', True, False, None),
(42, 'sonstige', 'Verbrauchsmaterialien', True, False, None),
(43, 'sonstige', 'Mietkosten Software', True, False, None),
(44, 'sonstige', 'Nebenkosten Geldverkehr', True, False, None),
(46, 'summe', 'Summe sonstige (ohne Pers., Abschr.)', False, True, '=computed'),
(47, 'summe', 'Gesamtkosten (Klasse 6)', False, True, '=computed'),
]
count = 0
for idx, (row, cat, label, editable, is_sum, formula) in enumerate(rows_config):
values = read_monthly_values(ws, row)
cur.execute("""
INSERT INTO fp_betriebliche_aufwendungen
(scenario_id, category, row_label, row_index, is_editable, is_sum_row,
formula_desc, values, excel_row, sort_order)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
""", (scenario_id, cat, label, row, editable, is_sum,
formula, Json(values), row, idx + 1))
count += 1
print(f" -> {count} Kostenposten importiert")
def import_investitionen(cur, ws, scenario_id):
"""Import Investitionen (rows 6-42)."""
print(" Importing Investitionen...")
count = 0
for i in range(37):
row = 6 + i
name = safe_str(ws.cell(row, 1).value)
amount = safe_float(ws.cell(row, 2).value)
purchase = safe_date(ws.cell(row, 3).value)
afa_years = safe_float(ws.cell(row, 4).value)
afa_end = safe_date(ws.cell(row, 5).value)
if not name and amount == 0:
continue
# Read monthly investment values (col G onwards in Investitionen sheet)
# This sheet has different column mapping — dates as headers
# For simplicity, store purchase amount and let engine compute AfA
cur.execute("""
INSERT INTO fp_investitionen
(scenario_id, item_name, category, purchase_amount, purchase_date,
afa_years, afa_end_date, is_editable,
values_invest, values_afa, excel_row, sort_order)
VALUES (%s, %s, %s, %s, %s, %s, %s, true, %s, %s, %s, %s)
""", (scenario_id, name, 'ausstattung' if 'Ausstattung' in name else 'gwg',
amount, purchase, int(afa_years) if afa_years else None, afa_end,
Json({}), Json({}), row, count + 1))
count += 1
print(f" -> {count} Investitionsgueter importiert")
def import_sonst_ertraege(cur, ws, scenario_id):
"""Import Sonst. betr. Ertraege (6 categories)."""
print(" Importing Sonst. betr. Ertraege...")
categories = [
(3, 8, 'Interne Kostenstellen'),
(9, 12, 'Entwicklung National'),
(13, 16, 'Entwicklung International'),
(17, 20, 'Beratungsdienstleistung'),
(21, 24, 'Zuwendungen'),
(25, 28, 'TBD'),
]
count = 0
for sum_row, end_row, cat_name in categories:
# Sum row
values = read_monthly_values(ws, sum_row)
cur.execute("""
INSERT INTO fp_sonst_ertraege
(scenario_id, category, row_label, row_index, is_editable, is_sum_row,
values, excel_row, sort_order)
VALUES (%s, %s, %s, %s, false, true, %s, %s, %s)
""", (scenario_id, cat_name, cat_name, sum_row, Json(values), sum_row, count + 1))
count += 1
# Detail rows
for r in range(sum_row + 1, end_row + 1):
values = read_monthly_values(ws, r)
label = safe_str(ws.cell(r, 1).value) or f'Position {r - sum_row}'
cur.execute("""
INSERT INTO fp_sonst_ertraege
(scenario_id, category, row_label, row_index, is_editable, is_sum_row,
values, excel_row, sort_order)
VALUES (%s, %s, %s, %s, true, false, %s, %s, %s)
""", (scenario_id, cat_name, label, r, Json(values), r, count + 1))
count += 1
# Total row (29)
values = read_monthly_values(ws, 29)
cur.execute("""
INSERT INTO fp_sonst_ertraege
(scenario_id, category, row_label, row_index, is_editable, is_sum_row,
values, excel_row, sort_order)
VALUES (%s, %s, %s, %s, false, true, %s, %s, %s)
""", (scenario_id, 'GESAMT', 'GESAMTUMSATZ', 29, Json(values), 29, count + 1))
print(f" -> {count + 1} Ertragsposten importiert")
def import_liquiditaet(cur, ws, scenario_id):
"""Import Liquiditaet (rows 4-27)."""
print(" Importing Liquiditaet...")
rows_config = [
(4, 'Umsatzerloese', 'einzahlung', False, '=Umsatzerloese!GESAMT'),
(5, 'Sonst. betriebl. Ertraege', 'einzahlung', False, '=Sonst.Ertraege!GESAMT'),
(6, 'Anzahlungen', 'einzahlung', True, None),
(7, 'Neuer Eigenkapitalzugang', 'einzahlung', True, None),
(8, 'Erhaltenes Fremdkapital', 'einzahlung', True, None),
(9, 'Summe EINZAHLUNGEN', 'einzahlung', False, '=SUM(4:8)'),
(12, 'Materialaufwand', 'auszahlung', False, '=Materialaufwand!SUMME'),
(13, 'Personalkosten', 'auszahlung', False, '=Personalkosten!Total'),
(14, 'Sonstige Kosten', 'auszahlung', False, '=Betriebliche!Summe_sonstige'),
(15, 'Kreditrueckzahlungen', 'auszahlung', True, None),
(16, 'Umsatzsteuer', 'auszahlung', True, None),
(17, 'Gewerbesteuer', 'auszahlung', True, None),
(18, 'Koerperschaftsteuer', 'auszahlung', True, None),
(19, 'Summe AUSZAHLUNGEN', 'auszahlung', False, '=SUM(12:18)'),
(21, 'UEBERSCHUSS VOR INVESTITIONEN', 'ueberschuss', False, '=Einzahlungen-Auszahlungen'),
(22, 'Investitionen', 'ueberschuss', False, '=Investitionen!Gesamt'),
(23, 'UEBERSCHUSS VOR ENTNAHMEN', 'ueberschuss', False, '=21-22'),
(24, 'Kapitalentnahmen/Ausschuettungen', 'ueberschuss', True, None),
(25, 'UEBERSCHUSS', 'ueberschuss', False, '=23-24'),
(26, 'Kontostand zu Beginn des Monats', 'kontostand', False, '=prev_month_liquiditaet'),
(27, 'LIQUIDITAET', 'kontostand', False, '=26+25'),
]
count = 0
for row, label, row_type, editable, formula in rows_config:
values = read_monthly_values(ws, row)
cur.execute("""
INSERT INTO fp_liquiditaet
(scenario_id, row_label, row_type, is_editable, formula_desc,
values, excel_row, sort_order)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
""", (scenario_id, label, row_type, editable, formula,
Json(values), row, count + 1))
count += 1
print(f" -> {count} Liquiditaetszeilen importiert")
def import_kunden(cur, ws, scenario_id):
"""Import Kunden (6 segments, rows 26-167)."""
print(" Importing Kunden...")
# Summary rows (4-23) = aggregation across segments
for i in range(20):
row = 4 + i
label = safe_str(ws.cell(row, 1).value)
if not label:
label = f'Produkt {i + 1}'
values = read_monthly_values(ws, row)
cur.execute("""
INSERT INTO fp_kunden_summary
(scenario_id, row_label, row_index, values, excel_row, sort_order)
VALUES (%s, %s, %s, %s, %s, %s)
""", (scenario_id, label, row, Json(values), row, i + 1))
# 6 segments, each starts 24 rows apart: 26, 50, 74, 98, 122, 146
segment_starts = [
(26, 'Care (Privat)'),
(50, 'Horse (Haendler)'),
(74, 'Segment 3'),
(98, 'Segment 4'),
(122, 'Segment 5'),
(146, 'Segment 6'),
]
# Read segment names from Excel
for idx, (start_row, default_name) in enumerate(segment_starts):
seg_name = safe_str(ws.cell(start_row, 1).value) or default_name
# Each segment: row+2 = header, row+2..row+21 = module rows
module_start = start_row + 2 # row 28, 52, 76, 100, 124, 148
count = 0
for m in range(20):
row = module_start + m
label = safe_str(ws.cell(row, 1).value)
if not label:
continue
pct = safe_float(ws.cell(row, 2).value)
pct_label = safe_str(ws.cell(row, 2).value)
values = read_monthly_values(ws, row)
# First module per segment (m=0) is the base editable input
is_base = (m == 0)
formula_type = None
if pct_label == 'gerechnet':
formula_type = 'cumulative'
elif pct > 0 and not is_base:
formula_type = 'rounddown_pct'
cur.execute("""
INSERT INTO fp_kunden
(scenario_id, segment_name, segment_index, row_label, row_index,
percentage, formula_type, is_editable, values, excel_row, sort_order)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
""", (scenario_id, seg_name, idx + 1, label, row,
pct if pct else None, formula_type, is_base,
Json(values), row, count + 1))
count += 1
print(f" -> Kunden importiert (6 Segmente)")
def import_umsatzerloese(cur, ws, scenario_id):
"""Import Umsatzerloese (revenue, quantity, prices)."""
print(" Importing Umsatzerloese...")
count = 0
# Revenue rows (3-23): computed = quantity * price
for i in range(21):
row = 3 + i
label = safe_str(ws.cell(row, 1).value) or f'Produkt {i+1}'
if not label.strip():
continue
values = read_monthly_values(ws, row)
cur.execute("""
INSERT INTO fp_umsatzerloese
(scenario_id, section, row_label, row_index, is_editable, values, excel_row, sort_order)
VALUES (%s, 'revenue', %s, %s, false, %s, %s, %s)
""", (scenario_id, label, row, Json(values), row, count + 1))
count += 1
# Total row (24)
values = read_monthly_values(ws, 24)
cur.execute("""
INSERT INTO fp_umsatzerloese
(scenario_id, section, row_label, row_index, is_editable, values, excel_row, sort_order)
VALUES (%s, 'revenue', 'GESAMTUMSATZ', 24, false, %s, 24, %s)
""", (scenario_id, Json(values), count + 1))
count += 1
# Quantity rows (27-46): from Kunden
for i in range(20):
row = 27 + i
label = safe_str(ws.cell(row, 1).value) or f'Produkt {i+1}'
if not label.strip():
continue
values = read_monthly_values(ws, row)
cur.execute("""
INSERT INTO fp_umsatzerloese
(scenario_id, section, row_label, row_index, is_editable, values, excel_row, sort_order)
VALUES (%s, 'quantity', %s, %s, false, %s, %s, %s)
""", (scenario_id, label, row, Json(values), row, count + 1))
count += 1
# Price rows (49-73): editable VK prices
for i in range(25):
row = 49 + i
label = safe_str(ws.cell(row, 1).value) or f'Produkt {i+1}'
if not label.strip():
continue
values = read_monthly_values(ws, row)
cur.execute("""
INSERT INTO fp_umsatzerloese
(scenario_id, section, row_label, row_index, is_editable, values, excel_row, sort_order)
VALUES (%s, 'price', %s, %s, true, %s, %s, %s)
""", (scenario_id, label, row, Json(values), row, count + 1))
count += 1
print(f" -> {count} Umsatzzeilen importiert")
def import_materialaufwand(cur, ws, scenario_id):
"""Import Materialaufwand (simplified: only Mac Mini + Mac Studio)."""
print(" Importing Materialaufwand...")
count = 0
# Cost rows (3-23): computed = quantity * unit_cost
for i in range(21):
row = 3 + i
label = safe_str(ws.cell(row, 1).value)
if not label:
continue
values = read_monthly_values(ws, row)
cur.execute("""
INSERT INTO fp_materialaufwand
(scenario_id, section, row_label, row_index, is_editable, values, excel_row, sort_order)
VALUES (%s, 'cost', %s, %s, false, %s, %s, %s)
""", (scenario_id, label, row, Json(values), row, count + 1))
count += 1
# Total (24)
values = read_monthly_values(ws, 24)
cur.execute("""
INSERT INTO fp_materialaufwand
(scenario_id, section, row_label, row_index, is_editable, values, excel_row, sort_order)
VALUES (%s, 'cost', 'SUMME', 24, false, %s, 24, %s)
""", (scenario_id, Json(values), count + 1))
count += 1
# Unit cost rows (51-73): editable EK prices
for i in range(23):
row = 51 + i
label = safe_str(ws.cell(row, 1).value)
if not label:
continue
values = read_monthly_values(ws, row)
cur.execute("""
INSERT INTO fp_materialaufwand
(scenario_id, section, row_label, row_index, is_editable, values, excel_row, sort_order)
VALUES (%s, 'unit_cost', %s, %s, true, %s, %s, %s)
""", (scenario_id, label, row, Json(values), row, count + 1))
count += 1
print(f" -> {count} Materialzeilen importiert (Mac Mini 3.200 / Mac Studio 13.000 EK)")
def import_guv(cur, ws, scenario_id):
"""Import GuV Jahresabschluss (annual summary)."""
print(" Importing GuV Jahresabschluss...")
# Annual columns: B=2026, C=2027, D=2028, E=2029, F=2030
year_cols = {2: 'y2026', 3: 'y2027', 4: 'y2028', 5: 'y2029', 6: 'y2030'}
rows_config = [
(3, 'Umsatzerloese', False, '=Umsatzerloese!Jahressumme'),
(4, 'Bestandsveraenderungen', True, None),
(5, 'Gesamtleistung', False, '=SUM(3:4)'),
(9, 'Sonst. betriebl. Ertraege', False, '=Sonst.Ertraege!Jahressumme'),
(10, 'Summe sonst. Ertraege', False, '=SUM(8:9)'),
(13, 'Materialaufwand Waren', False, '=Materialaufwand!Jahressumme'),
(14, 'Materialaufwand Leistungen', False, '=Materialaufwand!bezogene_Leistungen'),
(15, 'Summe Materialaufwand', False, '=13+14'),
(17, 'Rohergebnis', False, '=5+10-15'),
(20, 'Loehne und Gehaelter', False, '=Personalkosten!Brutto'),
(21, 'Soziale Abgaben', False, '=Personalkosten!Sozial'),
(22, 'Summe Personalaufwand', False, '=20+21'),
(25, 'Abschreibungen', False, '=Investitionen!AfA'),
(27, 'Sonst. betriebl. Aufwendungen', False, '=Betriebliche!Summe_sonstige'),
(29, 'EBIT', False, '=5+10-15-22-25-27'),
(31, 'Zinsertraege', True, None),
(33, 'Zinsaufwendungen', True, None),
(35, 'Steuern gesamt', False, '=36+37'),
(36, 'Koerperschaftssteuer', False, '=computed'),
(37, 'Gewerbesteuer', False, '=computed'),
(39, 'Ergebnis nach Steuern', False, '=29+31-33-35'),
(41, 'Sonstige Steuern', True, None),
(43, 'Jahresueberschuss', False, '=39-41'),
]
count = 0
for row, label, is_edit, formula in rows_config:
values = {}
for col, key in year_cols.items():
v = ws.cell(row, col).value
values[key] = round(float(v), 2) if v and not isinstance(v, str) else 0
cur.execute("""
INSERT INTO fp_guv
(scenario_id, row_label, row_index, is_sum_row, formula_desc,
values, excel_row, sort_order)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
""", (scenario_id, label, row, formula is not None and not is_edit,
formula, Json(values), row, count + 1))
count += 1
print(f" -> {count} GuV-Zeilen importiert")
def main():
if len(sys.argv) < 2:
print("Usage: python3 import-finanzplan.py <path-to-xlsm>")
sys.exit(1)
xlsx_path = sys.argv[1]
print(f"Opening: {xlsx_path}")
wb = openpyxl.load_workbook(xlsx_path, data_only=True)
print(f"Connecting to: {DB_URL.split('@')[1] if '@' in DB_URL else DB_URL}")
conn = psycopg2.connect(DB_URL)
cur = conn.cursor()
# Schema already applied separately — skip if tables exist
cur.execute("SELECT EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = 'fp_scenarios')")
if not cur.fetchone()[0]:
schema_path = os.path.join(os.path.dirname(__file__), '001_finanzplan_tables.sql')
print(f"Applying schema: {schema_path}")
with open(schema_path) as f:
cur.execute(f.read())
conn.commit()
else:
print("Schema already exists, skipping.")
# Get or create default scenario
cur.execute("SELECT id FROM fp_scenarios WHERE is_default = true LIMIT 1")
row = cur.fetchone()
if row:
scenario_id = row[0]
# Clear existing data for re-import
for table in ['fp_kunden', 'fp_kunden_summary', 'fp_umsatzerloese', 'fp_materialaufwand',
'fp_personalkosten', 'fp_betriebliche_aufwendungen', 'fp_investitionen',
'fp_sonst_ertraege', 'fp_liquiditaet', 'fp_guv']:
cur.execute(f"DELETE FROM {table} WHERE scenario_id = %s", (scenario_id,))
else:
cur.execute("INSERT INTO fp_scenarios (name, is_default) VALUES ('Base Case', true) RETURNING id")
scenario_id = cur.fetchone()[0]
print(f"Scenario ID: {scenario_id}")
print(f"\nImporting sheets...")
# Import each sheet
import_kunden(cur, wb['Kunden'], scenario_id)
import_umsatzerloese(cur, wb['Umsatzerlöse'], scenario_id)
import_materialaufwand(cur, wb['Materialaufwand'], scenario_id)
import_personalkosten(cur, wb['Personalkosten'], scenario_id)
import_betriebliche_aufwendungen(cur, wb['Betriebliche Aufwendungen'], scenario_id)
import_investitionen(cur, wb['Investitionen'], scenario_id)
import_sonst_ertraege(cur, wb['Sonst. betr. Erträge'], scenario_id)
import_liquiditaet(cur, wb['Liquidität'], scenario_id)
import_guv(cur, wb['GuV Jahresabschluss'], scenario_id)
conn.commit()
print(f"\nImport abgeschlossen!")
# Summary
for table in ['fp_kunden', 'fp_kunden_summary', 'fp_umsatzerloese', 'fp_materialaufwand',
'fp_personalkosten', 'fp_betriebliche_aufwendungen', 'fp_investitionen',
'fp_sonst_ertraege', 'fp_liquiditaet', 'fp_guv']:
cur.execute(f"SELECT COUNT(*) FROM {table} WHERE scenario_id = %s", (scenario_id,))
count = cur.fetchone()[0]
print(f" {table}: {count} Zeilen")
cur.close()
conn.close()
if __name__ == '__main__':
main()