fix: simplified fp-patch with error handling
Some checks failed
Build pitch-deck / build-push-deploy (push) Successful in 1m13s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 34s
CI / test-bqas (push) Has been cancelled

This commit is contained in:
Benjamin Admin
2026-04-21 23:11:28 +02:00
parent 2dfc47d67e
commit 72250c7c75

View File

@@ -5,76 +5,75 @@ import { computeFinanzplan } from '@/lib/finanzplan/engine'
export async function POST() { export async function POST() {
const WD = 'c0000000-0000-0000-0000-000000000200' const WD = 'c0000000-0000-0000-0000-000000000200'
const results: string[] = [] const results: string[] = []
const q = (sql: string, p?: unknown[]) => pool.query(sql, p)
// 1. Insurance updates try {
await q(`UPDATE fp_betriebliche_aufwendungen SET values=(SELECT jsonb_object_agg(key,CASE WHEN (value::text)::numeric>0 THEN to_jsonb(125) ELSE value END) FROM jsonb_each(values)) WHERE scenario_id=$1 AND row_label='D&O-Versicherung'`,[WD]) // 1. Insurance: D&O 125, Cyber 200, Rechtsschutz 100
await q(`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]) for (const [label, amt] of [['D&O-Versicherung',125],['Cyber-Versicherung',200],['Rechtsschutzversicherung',100]] as [string,number][]) {
await q(`DELETE FROM fp_betriebliche_aufwendungen WHERE scenario_id=$1 AND row_label='Produkthaftpflicht'`,[WD]) 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])
await q(`UPDATE fp_betriebliche_aufwendungen SET values=(SELECT jsonb_object_agg(key,CASE WHEN (value::text)::numeric>0 THEN to_jsonb(200) ELSE value END) FROM jsonb_each(values)) WHERE scenario_id=$1 AND row_label='Cyber-Versicherung'`,[WD]) if (rowCount && rowCount > 0) results.push(`${label}=${amt}`)
await q(`UPDATE fp_betriebliche_aufwendungen SET values=(SELECT jsonb_object_agg(key,CASE WHEN (value::text)::numeric>0 THEN to_jsonb(100) ELSE value END) FROM jsonb_each(values)) WHERE scenario_id=$1 AND row_label='Rechtsschutzversicherung'`,[WD])
results.push('Insurance updated')
// 2. New insurances (if not exist)
const newIns: [string,number][] = [['Betriebshaftpflicht',100],['Dienstreise-Krankenversicherung',15],['Gruppenunfallversicherung',40],['Schlüsselperson-Versicherung (Key Man)',150]]
for (const [label, amt] of newIns) {
const {rows}=await q(`SELECT id FROM fp_betriebliche_aufwendungen WHERE scenario_id=$1 AND row_label=$2`,[WD,label])
if (rows.length===0) {
const vals: Record<string,number> = {}; for(let m=8;m<=60;m++) vals[`m${m}`]=amt
await q(`INSERT INTO fp_betriebliche_aufwendungen (scenario_id,category,row_label,row_index,is_editable,is_sum_row,values,sort_order) VALUES ($1,'versicherungen',$2,$3,true,false,$4,$3)`,[WD,label,17+newIns.indexOf([label,amt] as never),JSON.stringify(vals)])
} }
}
// Simpler: just check and insert by label // 2. E&O → IT-Haftpflicht 375
for (const [label, amt, sort] of [['Betriebshaftpflicht',100,17],['Dienstreise-Krankenversicherung',15,18],['Gruppenunfallversicherung',40,19],['Schlüsselperson-Versicherung (Key Man)',150,20]] as [string,number,number][]) { 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])
const {rows}=await q(`SELECT id FROM fp_betriebliche_aufwendungen WHERE scenario_id=$1 AND row_label=$2`,[WD,label]) await pool.query(`DELETE FROM fp_betriebliche_aufwendungen WHERE scenario_id=$1 AND row_label='Produkthaftpflicht'`, [WD])
if (rows.length===0) { results.push('E&O→IT-Haftpflicht, Produkthaftpflicht deleted')
const vals: Record<string,number> = {}; for(let m=8;m<=60;m++) vals[`m${m}`]=amt
await q(`INSERT INTO fp_betriebliche_aufwendungen (scenario_id,category,row_label,row_index,is_editable,is_sum_row,values,sort_order) VALUES ($1,'versicherungen',$2,$3,true,false,$4,$3)`,[WD,label,sort,JSON.stringify(vals)]) // 3. Telefon → Internet/Mobilfunk
results.push(`ADD ${label}`) 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<string,number>,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}`)
}
} }
}
// 3. Rename Telefon → Internet/Mobilfunk // 6. Liquidität: Fördergelder + Forschungszulage
await q(`UPDATE fp_betriebliche_aufwendungen SET row_label='Internet/Mobilfunk (F)' WHERE scenario_id=$1 AND row_label='Telefon'`,[WD]) const liqRows: [string,Record<string,number>][] = [
['Fördergelder / Grants', Object.fromEntries(Array.from({length:48},(_,i)=>[`m${i+13}`,3000]))],
// 4. New positions: Recruiting, ext. DSB, Zertifizierung ['Forschungszulage (§ 27a EStG)', {
for (const [cat, label, vals, sort] of [ ...Object.fromEntries(Array.from({length:12},(_,i)=>[`m${i+13}`,2184])),
['sonstige','Recruiting / Stellenanzeigen', Object.fromEntries([...Array.from({length:17},(_,i)=>[`m${i+8}`,300]),...Array.from({length:36},(_,i)=>[`m${i+25}`,500])]), 21], ...Object.fromEntries(Array.from({length:12},(_,i)=>[`m${i+25}`,6268])),
['sonstige','Externer Datenschutzbeauftragter', Object.fromEntries(Array.from({length:53},(_,i)=>[`m${i+8}`,400])), 22], ...Object.fromEntries(Array.from({length:12},(_,i)=>[`m${i+37}`,9580])),
['besondere','Zertifizierung (ISO 27001 / BSI C5)', Object.fromEntries(Array.from({length:36},(_,i)=>[`m${i+25}`,1500])), 23], ...Object.fromEntries(Array.from({length:12},(_,i)=>[`m${i+49}`,12823])),
] as [string,string,Record<string,number>,number][]) { }],
const {rows}=await q(`SELECT id FROM fp_betriebliche_aufwendungen WHERE scenario_id=$1 AND row_label=$2`,[WD,label]) ]
if (rows.length===0) { for (const [label,vals] of liqRows) {
await q(`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)]) const {rows}=await pool.query(`SELECT id FROM fp_liquiditaet WHERE scenario_id=$1 AND row_label=$2`,[WD,label])
results.push(`ADD ${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}`)
}
} }
// 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
const r = await computeFinanzplan(pool, WD)
results.push(`WD cash_m60=${r.liquiditaet?.endstand?.m60}`)
} catch (err) {
results.push(`ERROR: ${err instanceof Error ? err.message : String(err)}`)
} }
// 5. Messen: double 2029, triple 2030
await q(`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')
// 6. Fördergelder + Forschungszulage in Liquidität
for (const [label, vals] of [
['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]))}],
] as [string,Record<string,number>][]) {
const {rows}=await q(`SELECT id FROM fp_liquiditaet WHERE scenario_id=$1 AND row_label=$2`,[WD,label])
if (rows.length===0) {
await q(`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}`)
}
}
// 7. GuV sort order + is_sum_row fixes
await q(`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','Zinsertraege','Zinsaufwendungen','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 q(`UPDATE fp_guv SET sort_order=$1 WHERE scenario_id=$2 AND row_label=$3`,[s,WD,l])
await q(`UPDATE fp_guv SET row_label=REPLACE(row_label,'Zinsertraege','Zinserträge') WHERE row_label LIKE '%Zinsertraege%'`)
// 8. Recompute
const r = await computeFinanzplan(pool, WD)
results.push(`WD cash_m60=${r.liquiditaet?.endstand?.m60}`)
return NextResponse.json({ ok: true, results }) return NextResponse.json({ ok: true, results })
} }