diff --git a/pitch-deck/app/api/admin/fp-patch/route.ts b/pitch-deck/app/api/admin/fp-patch/route.ts index de1d051..b087870 100644 --- a/pitch-deck/app/api/admin/fp-patch/route.ts +++ b/pitch-deck/app/api/admin/fp-patch/route.ts @@ -1,74 +1,40 @@ -import { NextResponse } from 'next/server' +import { NextRequest, NextResponse } from 'next/server' import pool from '@/lib/db' import { computeFinanzplan } from '@/lib/finanzplan/engine' -export async function POST() { +export async function POST(request: NextRequest) { const WD = 'c0000000-0000-0000-0000-000000000200' const results: string[] = [] + const body = await request.json().catch(() => ({})) try { - // 1. Insurance: D&O 125, Cyber 200, Rechtsschutz 100 - for (const [label, amt] of [['D&O-Versicherung',125],['Cyber-Versicherung',200],['Rechtsschutzversicherung',100]] as [string,number][]) { - const { rowCount } = await pool.query(`UPDATE fp_betriebliche_aufwendungen SET values=(SELECT jsonb_object_agg(key,CASE WHEN (value::text)::numeric>0 THEN to_jsonb($1::int) ELSE value END) FROM jsonb_each(values)) WHERE scenario_id=$2 AND row_label=$3`, [amt, WD, label]) - if (rowCount && rowCount > 0) results.push(`${label}=${amt}`) - } - - // 2. E&O → IT-Haftpflicht 375 - await pool.query(`UPDATE fp_betriebliche_aufwendungen SET row_label='IT-Haftpflicht (E&O + Produkt)', values=(SELECT jsonb_object_agg(key,CASE WHEN (value::text)::numeric>0 THEN to_jsonb(375) ELSE value END) FROM jsonb_each(values)) WHERE scenario_id=$1 AND row_label='E&O-Versicherung'`, [WD]) - await pool.query(`DELETE FROM fp_betriebliche_aufwendungen WHERE scenario_id=$1 AND row_label='Produkthaftpflicht'`, [WD]) - results.push('E&O→IT-Haftpflicht, Produkthaftpflicht deleted') - - // 3. Telefon → Internet/Mobilfunk - await pool.query(`UPDATE fp_betriebliche_aufwendungen SET row_label='Internet/Mobilfunk (F)' WHERE scenario_id=$1 AND row_label='Telefon'`, [WD]) - - // 4. Messen 2029/2030 - await pool.query(`UPDATE fp_betriebliche_aufwendungen SET values=(SELECT jsonb_object_agg(key,CASE WHEN key IN ('m37','m40','m43','m46') THEN to_jsonb(10000) WHEN key IN ('m49','m52','m55','m58') THEN to_jsonb(15000) ELSE value END) FROM jsonb_each(values)) WHERE scenario_id=$1 AND row_label ILIKE '%Messe%'`, [WD]) - results.push('Messen scaled') - - // 5. New betriebliche rows (skip if exists) - const newRows: [string,string,Record,number][] = [ - ['versicherungen','Betriebshaftpflicht', Object.fromEntries(Array.from({length:53},(_,i)=>[`m${i+8}`,100])), 17], - ['versicherungen','Dienstreise-Krankenversicherung', Object.fromEntries(Array.from({length:53},(_,i)=>[`m${i+8}`,15])), 18], - ['versicherungen','Gruppenunfallversicherung', Object.fromEntries(Array.from({length:53},(_,i)=>[`m${i+8}`,40])), 19], - ['versicherungen','Schlüsselperson-Versicherung (Key Man)', Object.fromEntries(Array.from({length:53},(_,i)=>[`m${i+8}`,150])), 20], - ['sonstige','Recruiting / Stellenanzeigen', {...Object.fromEntries(Array.from({length:17},(_,i)=>[`m${i+8}`,300])),...Object.fromEntries(Array.from({length:36},(_,i)=>[`m${i+25}`,500]))}, 21], - ['sonstige','Externer Datenschutzbeauftragter', Object.fromEntries(Array.from({length:53},(_,i)=>[`m${i+8}`,400])), 22], - ['besondere','Zertifizierung (ISO 27001 / BSI C5)', Object.fromEntries(Array.from({length:36},(_,i)=>[`m${i+25}`,1500])), 23], - ] - for (const [cat,label,vals,sort] of newRows) { - const {rows}=await pool.query(`SELECT id FROM fp_betriebliche_aufwendungen WHERE scenario_id=$1 AND row_label=$2`,[WD,label]) - if (rows.length===0) { - await pool.query(`INSERT INTO fp_betriebliche_aufwendungen (scenario_id,category,row_label,row_index,is_editable,is_sum_row,values,sort_order) VALUES ($1,$2,$3,$4,true,false,$5,$4)`,[WD,cat,label,sort,JSON.stringify(vals)]) - results.push(`ADD ${label}`) + // Insert betriebliche rows from body + if (body.betrieb) { + for (const [cat, label, vals, sort] of body.betrieb) { + const { rows } = await pool.query(`SELECT id FROM fp_betriebliche_aufwendungen WHERE scenario_id=$1 AND row_label=$2`, [WD, label]) + if (rows.length === 0) { + await pool.query(`INSERT INTO fp_betriebliche_aufwendungen (scenario_id,category,row_label,row_index,is_editable,is_sum_row,values,sort_order) VALUES ($1,$2,$3,$4,true,false,$5,$4)`, [WD, cat, label, sort, JSON.stringify(vals)]) + results.push(`ADD ${label}`) + } else { + results.push(`SKIP ${label} (exists)`) + } } } - // 6. Liquidität: Fördergelder + Forschungszulage - const liqRows: [string,Record][] = [ - ['Fördergelder / Grants', Object.fromEntries(Array.from({length:48},(_,i)=>[`m${i+13}`,3000]))], - ['Forschungszulage (§ 27a EStG)', { - ...Object.fromEntries(Array.from({length:12},(_,i)=>[`m${i+13}`,2184])), - ...Object.fromEntries(Array.from({length:12},(_,i)=>[`m${i+25}`,6268])), - ...Object.fromEntries(Array.from({length:12},(_,i)=>[`m${i+37}`,9580])), - ...Object.fromEntries(Array.from({length:12},(_,i)=>[`m${i+49}`,12823])), - }], - ] - for (const [label,vals] of liqRows) { - const {rows}=await pool.query(`SELECT id FROM fp_liquiditaet WHERE scenario_id=$1 AND row_label=$2`,[WD,label]) - if (rows.length===0) { - await pool.query(`INSERT INTO fp_liquiditaet (scenario_id,row_label,row_type,is_editable,values,sort_order) VALUES ($1,$2,'einzahlung',true,$3,3)`,[WD,label,JSON.stringify(vals)]) - results.push(`ADD liq: ${label}`) + // Insert liquidität rows from body + if (body.liq) { + for (const [label, vals] of body.liq) { + const { rows } = await pool.query(`SELECT id FROM fp_liquiditaet WHERE scenario_id=$1 AND row_label=$2`, [WD, label]) + if (rows.length === 0) { + await pool.query(`INSERT INTO fp_liquiditaet (scenario_id,row_label,row_type,is_editable,values,sort_order) VALUES ($1,$2,'einzahlung',true,$3,3)`, [WD, label, JSON.stringify(vals)]) + results.push(`ADD liq: ${label}`) + } else { + results.push(`SKIP liq: ${label} (exists)`) + } } } - // 7. GuV fixes - await pool.query(`UPDATE fp_guv SET is_sum_row=false WHERE scenario_id=$1 AND row_label IN ('Umsatzerlöse','Bestandsveränderungen','Sonst. betriebl. Erträge','Materialaufwand Waren','Materialaufwand Leistungen','Löhne und Gehälter','Soziale Abgaben','Abschreibungen','Sonst. betriebl. Aufwendungen','Körperschaftssteuer','Gewerbesteuer','Sonstige Steuern')`,[WD]) - for (const [l,s] of [['Materialaufwand Waren',4],['Materialaufwand Leistungen',5],['Summe Materialaufwand',6],['Rohergebnis',7],['Löhne und Gehälter',8],['Soziale Abgaben',9],['Summe Personalaufwand',10],['Abschreibungen',11],['Sonst. betriebl. Aufwendungen',12],['Sonst. betriebl. Erträge',13],['Summe sonst. Erträge',14],['EBIT',15]] as [string,number][]) - await pool.query(`UPDATE fp_guv SET sort_order=$1 WHERE scenario_id=$2 AND row_label=$3`,[s,WD,l]) - await pool.query(`UPDATE fp_guv SET row_label=REPLACE(row_label,'Zinsertraege','Zinserträge') WHERE row_label LIKE '%Zinsertraege%'`) - results.push('GuV fixed') - - // 8. Recompute + // Recompute const r = await computeFinanzplan(pool, WD) results.push(`WD cash_m60=${r.liquiditaet?.endstand?.m60}`) } catch (err) {