diff --git a/README.md b/README.md index 8665160..901a2c5 100644 --- a/README.md +++ b/README.md @@ -160,3 +160,12 @@ Bei der Rechenprüfung wurden zwei semantische Inkonsistenzen korrigiert: Die Ge ### v3.33 – Transferquote zu echten Einnahmen Die Transferanalyse startet ohne vorausgewählte Quell- oder Zielkonten. Ausgewählte Konten werden mit einem deutlich sichtbaren Häkchen markiert. Zusätzlich zeigt die Analyse das Verhältnis des ausgewählten Transfervolumens zu den echten Einnahmen der Quellkonten. Die Transferquote wird insgesamt, je Quellkonto und je einzelner Transferposition ausgewiesen. Zielkonten zeigen ergänzend den eingehenden Transferanteil im Verhältnis zu ihren echten Einnahmen. Eingehende Transfers selbst zählen dabei nie als echte Einnahmen; bei 0 EUR echten Einnahmen wird keine Prozentquote berechnet. + + +### v3.34 – vollständiges Statistik-Glossar + +Die Statistik erklärt jetzt sämtliche fachlichen Kennzahlen und Zwischenwerte direkt in der Oberfläche. Dazu gehören ausdrücklich auch Werte wie **Tatsächliche externe Zuführung**, **Kosten-Finanzierungsbedarf**, **Gemeinsame Deckung**, **Brutto-Kostenanteil**, **Ist-Beitrag**, **Anteil am Bedarf**, **Nettoveränderung Gemeinschaftsvermögen**, **Liquid verfügbar**, **Vermögensaufbau netto** sowie die einkommensbasierte Soll-/Ist-Sicht. Neben den einzelnen Info-Schaltflächen gibt es auf der Statistikseite die zentrale Ansicht **Alle Begriffe & Formeln**. Jede Definition enthält Bedeutung, Formel, enthaltene und ausgeschlossene Bewegungen, Interpretation und ein Zahlenbeispiel. Das Datenmodell und die Berechnungslogik bleiben unverändert. + +### v3.35 – geplante Betragsänderungen für Transfers + +Transfers unterstützen jetzt dieselben zukünftigen Betragsänderungen wie Eingänge und Ausgänge. Der Basisbetrag gilt bis zur ersten Änderung; jede Änderung wird ab ihrem Wirksamkeitsdatum verwendet. Die geänderten Transferbeträge fließen korrekt in Monats-/Jahresplanung, Kontoliquidität, Statistik, Transferanalyse, Gemeinschaftsfinanzierung und PDF/XLSX-Kontoauszüge ein. In der Bewegungsansicht werden geplante Änderungen als Badge und im Detail-Akkordeon angezeigt. Bestehende Transfers werden automatisch mit einem leeren `amountChanges`-Array normalisiert; das JSON-Datenmodell bleibt bei `version: 11`. diff --git a/public/sw.js b/public/sw.js index 57c6dde..18c097b 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,4 +1,4 @@ -const SHELL_CACHE='fixfin-shell-v3.33'; +const SHELL_CACHE='fixfin-shell-v3.35'; const DATA_CACHE='fixfin-data-v1'; const APP_SHELL=['/','/manifest.webmanifest','/icons/icon-192.png','/icons/icon-512.png','/icons/apple-touch-icon.png']; diff --git a/server/index.js b/server/index.js index c34dda6..09a573e 100644 --- a/server/index.js +++ b/server/index.js @@ -35,6 +35,7 @@ function normalizeCategorizedMovement(item={}) { } function normalizeIncome(item={}) { return { ...normalizeCategorizedMovement(item), amountChanges: normalizeAmountChanges(item.amountChanges) }; } function normalizeExpense(item={}) { return { ...normalizeCategorizedMovement(item), amountChanges: normalizeAmountChanges(item.amountChanges), pushoverReminder: item.pushoverReminder===true }; } +function normalizeTransfer(item={}) { return { ...normalizeCategorizedMovement(item), amountChanges: normalizeAmountChanges(item.amountChanges) }; } function normalizeScenarioAdjustment(item={}) { const type=validScenarioTypes.has(item.type)?item.type:'expense'; const base={id:item.id,name:item.name,type,amount:Number(item.amount||0),personIds:normalizePersonIds(item)}; @@ -52,7 +53,7 @@ function normalizeData(input){ persons:Array.isArray(input.persons)?input.persons.map(({id,name})=>({id,name})):[], incomes:Array.isArray(input.incomes)?input.incomes.map(normalizeIncome):input.incomes, expenses:Array.isArray(input.expenses)?input.expenses.map(normalizeExpense):input.expenses, - transfers:Array.isArray(input.transfers)?input.transfers.map(normalizeCategorizedMovement):input.transfers, + transfers:Array.isArray(input.transfers)?input.transfers.map(normalizeTransfer):input.transfers, scenarios:Array.isArray(input.scenarios)?input.scenarios.map(normalizeScenario):[], }; } @@ -88,7 +89,7 @@ function validateData(input){ if(a.ownership==='unassigned'&&a.ownerPersonIds.length)throw new Error(`Nicht zugeordnetes Konto '${a.name}' darf keine Eigentümer enthalten.`); } for(const collection of ['incomes','expenses'])for(const item of input[collection]){validateMovement(item);validateAmountChanges(item);if(!accountIds.has(item.accountId))throw new Error(`Unbekanntes Konto bei '${item.name}'.`);if(item.categoryId!==null&&item.categoryId!==undefined&&!categoryIds.has(item.categoryId))throw new Error(`Unbekannte Kategorie bei '${item.name}'.`);validatePersonAssignments(item,personIds);if(collection==='expenses'&&typeof item.pushoverReminder!=='boolean')throw new Error(`Ungültige Reminder-Einstellung bei '${item.name}'.`);if(collection==='expenses'&&item.pushoverReminder&&item.interval!=='monthly'&&item.interval!=='once'&&(item.dueMonth===null||item.dueMonth===undefined))throw new Error(`Für den Pushover-Reminder von '${item.name}' muss ein Fälligkeitsmonat gesetzt sein.`);if(collection==='expenses'&&item.pushoverReminder&&item.interval==='once'&&!item.oneTimeDate)throw new Error(`Für den Pushover-Reminder von '${item.name}' muss ein Datum gesetzt sein.`);} - for(const t of input.transfers){validateMovement(t);if(!accountIds.has(t.fromAccountId)||!accountIds.has(t.toAccountId))throw new Error(`Unbekanntes Konto beim Transfer '${t.name}'.`);if(t.fromAccountId===t.toAccountId)throw new Error('Quell- und Zielkonto müssen verschieden sein.');if(t.categoryId!==null&&t.categoryId!==undefined&&!categoryIds.has(t.categoryId))throw new Error(`Unbekannte Kategorie beim Transfer '${t.name}'.`);validatePersonAssignments(t,personIds,'Transfer');} + for(const t of input.transfers){validateMovement(t);validateAmountChanges(t);if(!accountIds.has(t.fromAccountId)||!accountIds.has(t.toAccountId))throw new Error(`Unbekanntes Konto beim Transfer '${t.name}'.`);if(t.fromAccountId===t.toAccountId)throw new Error('Quell- und Zielkonto müssen verschieden sein.');if(t.categoryId!==null&&t.categoryId!==undefined&&!categoryIds.has(t.categoryId))throw new Error(`Unbekannte Kategorie beim Transfer '${t.name}'.`);validatePersonAssignments(t,personIds,'Transfer');} const scenarioIds=new Set(); for(const s of input.scenarios){ if(!s.id||typeof s.id!=='string')throw new Error('Szenario ohne gültige ID.'); @@ -152,7 +153,7 @@ async function runPushoverReminderCheck(){ } async function ensureDataFile(){await fs.mkdir(dataDir,{recursive:true});try{await fs.access(dataFile);}catch{await atomicWrite(emptyData(),false);}} -async function readData(){await ensureDataFile();const raw=await fs.readFile(dataFile,'utf8');const parsed=JSON.parse(raw);const data=normalizeData(parsed);validateData(data);const movements=[...(parsed.incomes||[]),...(parsed.expenses||[]),...(parsed.transfers||[])];const scenarioAdjustments=(parsed.scenarios||[]).flatMap(s=>s.adjustments||[]);if(parsed.version!==11||!parsed.settings||!Array.isArray(parsed.categories)||!Array.isArray(parsed.persons)||!Array.isArray(parsed.scenarios)||movements.some(x=>!x.interval)||movements.some(x=>!Object.hasOwn(x,'categoryId'))||movements.some(x=>!Array.isArray(x.personIds)||Object.hasOwn(x,'personId'))||scenarioAdjustments.some(x=>!Array.isArray(x.personIds)||Object.hasOwn(x,'personId'))||(parsed.incomes||[]).some(x=>!Array.isArray(x.amountChanges))||(parsed.expenses||[]).some(x=>!Array.isArray(x.amountChanges)||!Object.hasOwn(x,'pushoverReminder'))||movements.some(x=>!Object.hasOwn(x,'endDate'))||movements.some(x=>x.interval==='once'&&!Object.hasOwn(x,'oneTimeDate'))||parsed.accounts?.some(a=>Object.hasOwn(a,'balance')||!Object.hasOwn(a,'ownership')||!Array.isArray(a.ownerPersonIds)||!Object.hasOwn(a,'accountFunction')))await atomicWrite(data,true);return data;} +async function readData(){await ensureDataFile();const raw=await fs.readFile(dataFile,'utf8');const parsed=JSON.parse(raw);const data=normalizeData(parsed);validateData(data);const movements=[...(parsed.incomes||[]),...(parsed.expenses||[]),...(parsed.transfers||[])];const scenarioAdjustments=(parsed.scenarios||[]).flatMap(s=>s.adjustments||[]);if(parsed.version!==11||!parsed.settings||!Array.isArray(parsed.categories)||!Array.isArray(parsed.persons)||!Array.isArray(parsed.scenarios)||movements.some(x=>!x.interval)||movements.some(x=>!Object.hasOwn(x,'categoryId'))||movements.some(x=>!Array.isArray(x.personIds)||Object.hasOwn(x,'personId'))||scenarioAdjustments.some(x=>!Array.isArray(x.personIds)||Object.hasOwn(x,'personId'))||(parsed.incomes||[]).some(x=>!Array.isArray(x.amountChanges))||(parsed.expenses||[]).some(x=>!Array.isArray(x.amountChanges)||!Object.hasOwn(x,'pushoverReminder'))||(parsed.transfers||[]).some(x=>!Array.isArray(x.amountChanges))||movements.some(x=>!Object.hasOwn(x,'endDate'))||movements.some(x=>x.interval==='once'&&!Object.hasOwn(x,'oneTimeDate'))||parsed.accounts?.some(a=>Object.hasOwn(a,'balance')||!Object.hasOwn(a,'ownership')||!Array.isArray(a.ownerPersonIds)||!Object.hasOwn(a,'accountFunction')))await atomicWrite(data,true);return data;} async function atomicWrite(input,makeBackup=true){const data=normalizeData(input);validateData(data);await fs.mkdir(dataDir,{recursive:true});const tmp=`${dataFile}.${process.pid}.${crypto.randomUUID()}.tmp`;const payload=JSON.stringify(data,null,2)+'\n';if(makeBackup){try{await fs.copyFile(dataFile,backupFile);}catch(error){if(error.code!=='ENOENT')throw error;}}await fs.writeFile(tmp,payload,{encoding:'utf8',mode:0o600});await fs.rename(tmp,dataFile);} app.disable('x-powered-by');app.use(express.json({limit:'1mb'}));app.get('/api/health',(_req,res)=>res.json({ok:true}));app.get('/api/data',async(_req,res,next)=>{try{res.json(await readData());}catch(e){next(e);}});app.put('/api/data',async(req,res,next)=>{try{await atomicWrite(req.body);res.json(await readData());}catch(e){next(e);}});app.get('/api/export',async(_req,res,next)=>{try{const data=await readData();res.setHeader('Content-Disposition','attachment; filename="fixfin-backup.json"');res.type('application/json').send(JSON.stringify(data,null,2)+'\n');}catch(e){next(e);}});app.get('/api/report.xlsx',async(req,res,next)=>{try{const data=await readData();const year=Number(req.query.year)||new Date().getFullYear();const accountId=typeof req.query.account==='string'?req.query.account:'';const personId=typeof req.query.person==='string'&&req.query.person?req.query.person:'all';const includeTransfers=req.query.transfers!=='0';const account=data.accounts.find(a=>a.id===accountId);if(!account)throw new Error('Bitte ein Konto für den Kontoauszug auswählen.');if(personId!=='all'&&!data.persons.some(p=>p.id===personId))throw new Error('Bitte eine gültige Person für den Kontoauszug auswählen.');const buffer=await createExcelReport(data,{year,accountId,personId,includeTransfers});const suffix=account.name.replace(/[^a-zA-Z0-9äöüÄÖÜß_-]+/g,'-');const personSuffix=personId==='all'?'':`-${data.persons.find(p=>p.id===personId)?.name||'Person'}`.replace(/[^a-zA-Z0-9äöüÄÖÜß_-]+/g,'-');res.setHeader('Content-Disposition',`attachment; filename="fixfin-kontoauszug-${year}-${suffix}${personSuffix}.xlsx"`);res.type('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet').send(Buffer.from(buffer));}catch(e){next(e);}});app.get('/api/report.pdf',async(req,res,next)=>{try{const data=await readData();const year=Number(req.query.year)||new Date().getFullYear();const accountId=typeof req.query.account==='string'?req.query.account:'';const personId=typeof req.query.person==='string'&&req.query.person?req.query.person:'all';const includeTransfers=req.query.transfers!=='0';const account=data.accounts.find(a=>a.id===accountId);if(!account)throw new Error('Bitte ein Konto für den Kontoauszug auswählen.');if(personId!=='all'&&!data.persons.some(p=>p.id===personId))throw new Error('Bitte eine gültige Person für den Kontoauszug auswählen.');const buffer=await createPdfReport(data,{year,accountId,personId,includeTransfers});const suffix=account.name.replace(/[^a-zA-Z0-9äöüÄÖÜß_-]+/g,'-');const personSuffix=personId==='all'?'':`-${data.persons.find(p=>p.id===personId)?.name||'Person'}`.replace(/[^a-zA-Z0-9äöüÄÖÜß_-]+/g,'-');res.setHeader('Content-Disposition',`attachment; filename="fixfin-kontoauszug-${year}-${suffix}${personSuffix}.pdf"`);res.type('application/pdf').send(buffer);}catch(e){next(e);}});app.get('/api/pushover/config',async(_req,res,next)=>{try{const config=await readPushoverConfig();res.json(config?{configured:true,userMasked:maskKey(config.user)}:{configured:false,userMasked:''});}catch(e){next(e);}});app.put('/api/pushover/config',async(req,res,next)=>{try{res.json(await savePushoverConfig(req.body));}catch(e){next(e);}});app.delete('/api/pushover/config',async(_req,res,next)=>{try{await fs.rm(pushoverFile,{force:true});res.json({configured:false,userMasked:''});}catch(e){next(e);}});app.post('/api/pushover/test',async(_req,res,next)=>{try{const config=await readPushoverConfig();if(!config)throw new Error('Pushover ist noch nicht konfiguriert.');await sendPushover(config,{title:'FixFin · Test',message:'Pushover ist erfolgreich mit FixFin verbunden.'});res.json({ok:true});}catch(e){next(e);}}); const dist=path.resolve(__dirname,'../dist');app.use(express.static(dist));app.use((_req,res)=>res.sendFile(path.join(dist,'index.html')));app.use((error,_req,res,_next)=>{console.error(error);res.status(400).json({error:error.message||'Unbekannter Fehler'});});await ensureDataFile();app.listen(port,'0.0.0.0',()=>{console.log(`FixFin läuft auf Port ${port}`);console.log(`Datendatei: ${dataFile}`);console.log(`Pushover-Reminder: Prüfung am 1. des Monats ab ${String(reminderHour).padStart(2,'0')}:00 Uhr (${process.env.TZ||'System-Zeitzone'})`);});setTimeout(()=>runPushoverReminderCheck().catch(error=>console.error('[Pushover] Reminder-Prüfung fehlgeschlagen:',error)),5000);setInterval(()=>runPushoverReminderCheck().catch(error=>console.error('[Pushover] Reminder-Prüfung fehlgeschlagen:',error)),15*60*1000); diff --git a/server/reports.js b/server/reports.js index 329f14e..f959b0d 100644 --- a/server/reports.js +++ b/server/reports.js @@ -57,7 +57,7 @@ export function buildAnnualForecast(data,year,accountId,personId='all',includeTr if(includeTransfers) for(const item of data.transfers||[]){ const factor=transferFactorForAccount(data,item,personId,accountId); if(!factor||(item.fromAccountId!==accountId&&item.toAccountId!==accountId))continue; for(let m=0;m<12;m++)if(occursInMonth(item,year,m)){ - const amount=Number(item.amount||0)*factor; + const amount=effectiveAmount(item,year,m)*factor; if(item.toAccountId===accountId){rows[m].transferIn+=amount;rows[m].items.push({...item,kind:'transfer',direction:'in',reportAmount:amount});} if(item.fromAccountId===accountId){rows[m].transferOut+=amount;rows[m].items.push({...item,kind:'transfer',direction:'out',reportAmount:amount});} } @@ -69,7 +69,7 @@ export function buildAnnualForecast(data,year,accountId,personId='all',includeTr function occurrenceRows(data,year,accountId,personId='all',includeTransfers=true){ const result=[]; const push=(item,kind,monthIndex,direction='',factor=1)=>{ - const amount=(kind==='transfer'?Number(item.amount||0):effectiveAmount(item,year,monthIndex))*factor; + const amount=effectiveAmount(item,year,monthIndex)*factor; result.push({monthIndex,month:MONTHS[monthIndex],kind,direction,name:item.name,person:kind==='transfer'?transferPersonLabel(data,item):personLabel(data,item),category:categoryName(data,item.categoryId),amount}); }; for(const item of data.incomes||[]){const factor=personFactor(item,personId);if(factor&&item.accountId===accountId)for(let m=0;m<12;m++)if(occursInMonth(item,year,m))push(item,'income',m,'',factor);} diff --git a/src/main.jsx b/src/main.jsx index ca29dc9..69f2954 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -266,7 +266,7 @@ function annualForecast(data, accountId = 'all', year = new Date().getFullYear() if (includeTransfers) for (const item of data.transfers.filter(relevantTransfer)) { const factor=transferPersonFactor(data,item,personId,accountId); for (let m=0;m<12;m++) if (occursInMonth(item,m,year)) { - const amount = Number(item.amount || 0) * factor; + const amount = effectiveAmount(item,year,m) * factor; if (isAll) { rows[m].transferIn += amount; rows[m].transferOut += amount; rows[m].items.push({ ...item, kind:'transfer', direction:'neutral', forecastAmount:amount }); } else { @@ -555,6 +555,80 @@ function personFlowStats(data, accountId = 'all', personId = 'all', includeTrans + +const STATS_GLOSSARY = { + monthlyAverage:{section:'Grundlagen',title:'Ø / Monat',meaning:'Ein vergleichbarer Monatsdurchschnitt für das gewählte Kalenderjahr. Er ist kein zwingend tatsächlich in jedem Monat gebuchter Betrag.',formula:'Im gewählten Jahr eingeplanter Gesamtbetrag ÷ 12.',includes:'Monatliche, vierteljährliche, halbjährliche, jährliche und einmalige Positionen, jeweils nur soweit sie im gewählten Jahr wirksam sind.',excludes:'Monate außerhalb des gewählten Jahres. Einmalige Positionen aus anderen Jahren.',interpretation:'Der Wert eignet sich zum Vergleichen und zur langfristigen Planung. Für die konkrete Belastung eines bestimmten Monats ist die Jahresplanung maßgeblich.',example:'Eine Jahresversicherung von 1.200 € ergibt 100 € Ø / Monat. Tatsächlich können die gesamten 1.200 € im März fällig sein.'}, + annualAmount:{section:'Grundlagen',title:'Betrag / Jahr',meaning:'Der im gewählten Kalenderjahr insgesamt eingeplante Betrag einer Kennzahl oder Position.',formula:'Summe aller im gewählten Jahr wirksamen Fälligkeiten bzw. Monatsbeträge.',includes:'Nur den gewählten Jahreszeitraum, inklusive einmaliger Positionen in diesem Jahr und zeitanteilig wirksamer Betragsänderungen oder Enddaten.',excludes:'Beträge außerhalb des Jahres.',interpretation:'Im Gegensatz zu Ø / Monat zeigt dieser Wert die gesamte Jahreswirkung. Bei einer monatlichen Position ist er typischerweise Monatswert × 12, bei Einmalig exakt der einmalige Betrag.',example:'100 € monatlich ergeben 1.200 € / Jahr. Eine einmalige Ausgabe von 750 € ergibt 750 € / Jahr.'}, + percentPoints:{section:'Grundlagen',title:'Prozentpunkte',meaning:'Direkte Differenz zwischen zwei Prozentwerten. Prozentpunkte sind nicht dasselbe wie eine prozentuale Veränderung.',formula:'Ist-% − Soll-%.',includes:'Die beiden verglichenen Prozentanteile.',excludes:'Keine relative Prozentänderung des Sollwerts.',interpretation:'+5 Prozentpunkte bedeutet zum Beispiel 55 % statt 50 %, nicht 5 % mehr im relativen Sinn.',example:'Ist 57,1 % und Soll 54,5 % ergeben +2,6 Prozentpunkte.'}, + personSplit:{section:'Grundlagen',title:'Personenaufteilung einer Bewegung',meaning:'Wie FixFin eine einzelne Bewegung mehreren zugeordneten Personen zurechnet.',formula:'Betrag ÷ Anzahl der zugeordneten Personen, sofern die jeweilige Statistik auf Bewegungs-Personenzuordnung basiert.',includes:'Ausgaben und in Einzelkonto-Sichten auch Eingänge, wenn mehrere Personen an der Bewegung hinterlegt sind.',excludes:'Persönliches Einkommen in der Gesamt-Personensicht. Dort folgt Einkommen dem Eigentümer des persönlichen Kontos.',interpretation:'Die Aufteilung beschreibt statistische Zuordnung, nicht zwingend wer den Banktransfer technisch ausgeführt hat.',example:'120 € Ausgabe mit drei Personen ergibt 40 € statistischen Anteil je Person.'}, + transferDetailsToggle:{section:'Grundlagen',title:'Transferdetails anzeigen',meaning:'Steuert nur, ob Transfer- und Finanzierungsdetails in der Statistik sichtbar sind.',formula:'Keine eigene Berechnung. Die zugrunde liegende Kontoliquidität bleibt identisch.',includes:'Sichtbarkeit von Transferblöcken, Ist-Beiträgen und Transferklassifikationen.',excludes:'Keine Änderung an echten Einnahmen, echten Kosten oder dem Liquiditäts-Nettofluss.',interpretation:'Ausblenden vereinfacht die Anzeige, darf aber keine rechnerisch andere Kontoliquidität erzeugen.',example:'Ein Konto mit 500 € Transfer raus behält denselben Nettofluss, egal ob Transferdetails ein- oder ausgeblendet sind.'}, + realIncome:{section:'Grundlagen',title:'Echte Eingänge / Einnahmen',meaning:'Geld, das wirtschaftlich neu in den betrachteten Bereich kommt und als Einnahme angelegt wurde.',formula:'Summe der geplanten Eingangspositionen der Auswahl; in Ø-Sichten jeweils Jahresbetrag ÷ 12.',includes:'Als Eingang angelegte Gehälter, Kindergeld, Renten, sonstige Einnahmen und einmalige Einnahmen, soweit sie zur Auswahl gehören.',excludes:'Transfers zwischen eigenen Konten. Eine Umbuchung vom Girokonto auf ein anderes Konto ist keine neue Einnahme.',interpretation:'Zeigt echte wirtschaftliche Zuflüsse. In einer Einzelkonto-Liquiditätssicht kommen Transfers rein separat hinzu.',example:'3.000 € Gehalt plus 200 € Nebeneinnahme ergeben 3.200 € echte Eingänge. Ein Transfer von 500 € vom eigenen Sparkonto zählt nicht dazu.'}, + realExpense:{section:'Grundlagen',title:'Echte Ausgaben / Kosten',meaning:'Geld, das wirtschaftlich verbraucht wird und als Ausgabe angelegt wurde.',formula:'Summe der geplanten Ausgaben der Auswahl; in Ø-Sichten jeweils Jahresbetrag ÷ 12.',includes:'Miete, Strom, Versicherungen, Lebensmittelpauschalen und andere als Ausgabe angelegte Positionen.',excludes:'Transfers zu Rücklage, Depot, Bausparer, Gemeinschaftskonto oder anderen eigenen Konten. Diese verändern Liquidität, sind aber keine Kosten.',interpretation:'Zeigt den tatsächlichen wirtschaftlichen Verbrauch. Deshalb bleibt die Kostenstatistik von reinen Umbuchungen frei.',example:'500 € Kreditrate als Ausgabe zählt als Kosten. 500 € vom Girokonto aufs Depot sind dagegen ein Transfer und keine Kosten.'}, + transferIn:{section:'Liquidität',title:'Transfers rein',meaning:'Geld, das durch eine interne Umbuchung auf das betrachtete Konto oder in den betrachteten Bereich gelangt.',formula:'Summe aller Transfers, deren Ziel das betrachtete Konto bzw. der Bereich ist.',includes:'Umbuchungen von anderen eigenen, persönlichen oder gemeinsamen Konten, soweit sie den Filter treffen.',excludes:'Echte Einnahmen. Diese werden separat als Eingänge gezeigt.',interpretation:'Erhöht die Kontoliquidität, aber nicht das wirtschaftliche Einkommen des Haushalts.',example:'500 € vom Sparkonto aufs Girokonto erhöhen Transfers rein um 500 €, nicht die echten Einnahmen.'}, + transferOut:{section:'Liquidität',title:'Transfers raus',meaning:'Geld, das durch eine interne Umbuchung das betrachtete Konto oder den betrachteten Bereich verlässt.',formula:'Summe aller Transfers, deren Quelle das betrachtete Konto bzw. der Bereich ist.',includes:'Umbuchungen zu Gemeinschaftskonto, Depot, Rücklage, Bausparen oder anderen Konten.',excludes:'Echte Ausgaben. Diese werden separat als Kosten gezeigt.',interpretation:'Senkt die Liquidität des Quellkontos, ohne automatisch eine wirtschaftliche Ausgabe zu erzeugen.',example:'300 € Giro → Depot senken den Giro-Nettofluss um 300 €, sind aber keine 300 € Kosten.'}, + accountNetFlow:{section:'Liquidität',title:'Liquiditäts-Nettofluss',meaning:'Die tatsächliche geplante Veränderung der Liquidität eines ausgewählten Kontos innerhalb des betrachteten Zeitraums.',formula:'Echte Eingänge + Transfers rein − echte Ausgaben − Transfers raus.',includes:'Alle echten Ein- und Ausgänge sowie alle Transfers, die das ausgewählte Konto berühren.',excludes:'Bewegungen auf anderen Konten, die das ausgewählte Konto nicht berühren.',interpretation:'Positiv bedeutet geplanten Liquiditätszuwachs auf dem Konto, negativ eine geplante Unterdeckung bzw. einen Abflussüberschuss.',example:'3.000 € Eingänge + 200 € Transfer rein − 1.500 € Kosten − 700 € Transfer raus = +1.000 € Nettofluss.'}, + economicBalance:{section:'Grundlagen',title:'Wirtschaftlicher Saldo',meaning:'Was nach echten Einnahmen und echten Kosten wirtschaftlich übrig bleibt, unabhängig davon, auf welche eigenen Konten das Geld verschoben wird.',formula:'Echte Einnahmen − echte Kosten.',includes:'Nur wirtschaftliche Einnahmen und Ausgaben.',excludes:'Alle Transfers. Transfers ändern nur den Ort des Geldes, nicht den wirtschaftlichen Gesamtsaldo.',interpretation:'Der Wert zeigt, ob der Plan strukturell Überschuss oder Defizit erzeugt.',example:'4.000 € Einnahmen minus 3.200 € Kosten ergeben +800 € wirtschaftlichen Saldo, auch wenn 600 € davon anschließend aufs Depot überwiesen werden.'}, + costRatio:{section:'Grundlagen',title:'Kostenquote',meaning:'Welcher Anteil der echten Einnahmen durch echte Kosten gebunden ist.',formula:'Echte Kosten ÷ echte Einnahmen × 100.',includes:'Nur echte Einnahmen und echte Kosten des gewählten Jahres.',excludes:'Transfers und reine Vermögensverschiebungen.',interpretation:'80 % bedeutet: 80 % der echten Einnahmen werden durch echte Kosten verbraucht. 100 % bedeutet vollständige Bindung, über 100 % ein wirtschaftliches Defizit.',example:'3.200 € Kosten bei 4.000 € Einnahmen ergeben 80 % Kostenquote.'}, + periodicReserve:{section:'Grundlagen',title:'Rücklagenbedarf periodische Kosten',meaning:'Welcher durchschnittliche Monatsbetrag zurückgelegt werden müsste, um nicht monatlich fällige echte Kosten über das Jahr zu finanzieren.',formula:'Jahresbetrag aller vierteljährlichen, halbjährlichen und jährlichen Ausgaben ÷ 12.',includes:'Periodische echte Ausgaben mit Intervall vierteljährlich, halbjährlich oder jährlich.',excludes:'Monatliche Ausgaben, einmalige Ausgaben und Transfers.',interpretation:'Der Wert ist eine Spar-/Rücklagenorientierung, nicht die tatsächliche Belastung jedes Monats.',example:'1.200 € Jahresversicherung plus 600 € halbjährliche Gesamtbelastung = 1.800 € pro Jahr, also 150 € Rücklagenbedarf Ø / Monat.'}, + expensiveMonth:{section:'Grundlagen',title:'Teuerster Monat',meaning:'Der Monat des gewählten Jahres mit den höchsten tatsächlich in diesem Monat fälligen echten Ausgaben.',formula:'Für jeden Monat werden alle dort fälligen echten Ausgaben summiert; der höchste Monatswert gewinnt.',includes:'Monatliche, konkret fällige periodische und einmalige Ausgaben.',excludes:'Transfers.',interpretation:'Hilft, Liquiditätsspitzen zu erkennen. Er ist nicht mit dem Ø-Monatswert zu verwechseln.',example:'Wenn im März zusätzlich 1.200 € Jahresversicherung fällig sind, kann März trotz gleicher laufender Kosten teuerster Monat werden.'}, + cheapMonth:{section:'Grundlagen',title:'Günstigster Monat',meaning:'Der Monat des gewählten Jahres mit den niedrigsten tatsächlich fälligen echten Ausgaben.',formula:'Für jeden Monat werden echte fällige Ausgaben summiert; der niedrigste Monatswert wird angezeigt.',includes:'Alle im jeweiligen Monat fälligen echten Ausgaben.',excludes:'Transfers.',interpretation:'Zeigt den Monat mit der geringsten geplanten Kostenbelastung. Ein Monat ohne Kosten kann 0 € erreichen.',example:'Wenn August nur 2.000 € laufende Kosten hat und keine Sonderfälligkeiten, kann er günstiger als Monate mit Versicherungsfälligkeiten sein.'}, + personalIncome:{section:'Persönliche Sicht',title:'Eigene Kontoeingänge / persönliches Einkommen',meaning:'Echte Einnahmen auf persönlichen Konten, die der betreffenden Person als Eigentümer gehören.',formula:'Summe der echten Eingänge auf persönlichen Konten dieser Person, als Jahreswert bzw. Jahreswert ÷ 12.',includes:'Einnahmen auf persönlichen Konten der Person.',excludes:'Gemeinsame Einnahmen auf Gemeinschaftskonten und Transfers.',interpretation:'Dies ist die persönliche Einkommensbasis. Gemeinsames Kindergeld wird beispielsweise nicht künstlich auf Personen verteilt.',example:'3.000 € Gehalt auf dem persönlichen Girokonto zählen vollständig als persönliches Einkommen. 500 € Kindergeld auf dem Gemeinschaftskonto nicht.'}, + grossCostShare:{section:'Persönliche Sicht',title:'Brutto-Kostenanteil',meaning:'Der Anteil einer Person an allen echten Kosten, bevor gemeinsame Einnahmen entlastend berücksichtigt werden.',formula:'Jede echte Ausgabe wird entsprechend ihrer Personenzuordnung verteilt. Bei zwei zugeordneten Personen erhält jede 50 %, bei drei jede 1/3.',includes:'Persönliche und gemeinsame echte Ausgaben, soweit die Person der Bewegung zugeordnet ist.',excludes:'Transfers und gemeinsame Einnahmen.',interpretation:'Zeigt die rechnerische Kostenverantwortung vor gemeinsamer Deckung.',example:'Eine gemeinsame Ausgabe von 1.200 € mit zwei Personen belastet jede Person mit 600 € Brutto-Kostenanteil.'}, + sharedCoverage:{section:'Persönliche Sicht',title:'Gemeinsame Deckung',meaning:'Der Teil der gemeinsamen Einnahmen, der rechnerisch den gemeinsamen Kostenanteil einer Person entlastet.',formula:'Gemeinsame Einnahmen werden höchstens bis zur Höhe der gemeinsamen Kosten berücksichtigt und proportional zu den jeweiligen Brutto-Kostenanteilen im Gemeinschaftsbereich verteilt.',includes:'Echte Einnahmen auf Gemeinschaftskonten desselben Eigentümerkreises, z. B. Kindergeld.',excludes:'Persönliche Einkommen und Transfers in den Gemeinschaftsbereich.',interpretation:'Die Zahl ist keine persönliche Einnahme. Sie reduziert nur die Kostenlast, die privat finanziert werden muss.',example:'500 € gemeinsame Einnahmen bei 1.200 € gemeinsamen Kosten und 50/50-Kostenanteilen bedeuten 250 € gemeinsame Deckung je Person.'}, + netCost:{section:'Persönliche Sicht',title:'Netto-Kostenlast',meaning:'Der echte Kostenanteil einer Person nach Entlastung durch gemeinsame Einnahmen.',formula:'max(0, Brutto-Kostenanteil − gemeinsame Deckung).',includes:'Persönlich zugeordnete echte Kosten abzüglich der rechnerischen Deckung aus gemeinsamen Einnahmen.',excludes:'Transfers. Eine Depotüberweisung erhöht die Netto-Kostenlast nicht.',interpretation:'Das ist die wirtschaftliche Kostenbelastung, die aus persönlichen Mitteln getragen werden muss.',example:'1.600 € Brutto-Kostenanteil minus 250 € gemeinsame Deckung ergeben 1.350 € Netto-Kostenlast.'}, + economicAvailable:{section:'Persönliche Sicht',title:'Wirtschaftlich verfügbar',meaning:'Was vom persönlichen echten Einkommen nach der persönlichen Netto-Kostenlast wirtschaftlich übrig bleibt.',formula:'Persönliches Einkommen − Netto-Kostenlast.',includes:'Persönliche echte Einnahmen und persönliche Netto-Kostenlast.',excludes:'Reine Transfers zwischen eigenen Konten. Sie verändern diesen wirtschaftlichen Wert nicht.',interpretation:'Zeigt den persönlichen strukturellen Überschuss vor der Frage, auf welchem Konto das Geld liegt.',example:'3.000 € persönliches Einkommen minus 1.350 € Netto-Kostenlast ergeben 1.650 € wirtschaftlich verfügbar.'}, + liquidAvailable:{section:'Persönliche Sicht',title:'Liquid verfügbar',meaning:'Was auf den persönlichen Zahlungsverkehrskonten einer Person nach allen dort geplanten Kontobewegungen übrig bleibt.',formula:'Echte Eingänge auf persönlichen Nicht-Vermögenskonten − echte Ausgaben + Transfers rein − Transfers raus.',includes:'Persönliche Giro-/Zahlungsverkehrskonten und alle diese Konten berührenden Transfers.',excludes:'Bestände oder Marktwerte von Depot, Rücklage und Bausparen. FixFin führt keine echten Kontostände.',interpretation:'Dieser Wert kann niedriger als wirtschaftlich verfügbar sein, wenn viel Geld in Depot, Rücklage oder Gemeinschaft verschoben wird.',example:'1.650 € wirtschaftlicher Überschuss, davon 300 € Depot und 350 € Gemeinschaftsfinanzierung, kann zu deutlich niedrigerer liquider Verfügbarkeit führen.'}, + liquidTransferIn:{section:'Persönliche Sicht',title:'Liquid-Transfers rein',meaning:'Transfers, die auf persönliche Zahlungsverkehrskonten der Person eingehen.',formula:'Summe der Transfers mit Ziel auf einem persönlichen Nicht-Vermögenskonto der Person.',includes:'Zum Beispiel Rücküberweisungen vom eigenen Sparkonto oder Geld von einer anderen Person auf das persönliche Girokonto.',excludes:'Echte Einnahmen und Transfers direkt in Depot/Rücklage.',interpretation:'Erhöht die verfügbare Liquidität, aber nicht das wirtschaftliche Einkommen.',example:'200 € Rücktransfer vom eigenen Sparkonto aufs Girokonto erhöhen Liquid-Transfers rein um 200 €.'}, + liquidTransferOut:{section:'Persönliche Sicht',title:'Liquid-Transfers raus',meaning:'Transfers, die von persönlichen Zahlungsverkehrskonten der Person abgehen.',formula:'Summe der Transfers mit Quelle auf einem persönlichen Nicht-Vermögenskonto der Person.',includes:'Depot-Sparraten, Rücklagenzuführungen, Gemeinschaftsfinanzierung und Transfers an andere Personen.',excludes:'Echte Ausgaben.',interpretation:'Senkt die liquide Verfügbarkeit. Wirtschaftlich kann das Geld weiterhin Vermögen oder Gemeinschaftsvermögen sein.',example:'200 € ins Depot plus 350 € aufs Gemeinschaftskonto ergeben 550 € Liquid-Transfers raus.'}, + sharedFunded:{section:'Persönliche Sicht',title:'Gemeinschaft finanziert',meaning:'Der tatsächliche persönliche Finanzierungsbeitrag einer Person in einen Gemeinschaftsbereich.',formula:'Summe der Transfers von persönlichen Konten dieser Person auf Konten des Gemeinschaftsbereichs.',includes:'Persönlich → gemeinsam, zugerechnet über den Eigentümer des Quellkontos.',excludes:'Gemeinsame Einnahmen, interne gemeinsame Umbuchungen und Transfers aus nicht persönlichen Quellen.',interpretation:'Zeigt, wie viel die Person tatsächlich in die Gemeinschaft eingezahlt hat. Das kann vom Soll abweichen.',example:'350 € vom persönlichen Girokonto aufs Gemeinschaftskonto ergeben 350 € Gemeinschaft finanziert.'}, + assetBuild:{section:'Persönliche Sicht',title:'Vermögensaufbau netto',meaning:'Nettozuführung vom persönlichen Zahlungsverkehr in persönliche Vermögenskonten desselben Eigentümers.',formula:'Zuführungen Zahlungsverkehr → Rücklage/Depot/Bausparen minus Entnahmen Rücklage/Depot/Bausparen → Zahlungsverkehr. Umschichtungen zwischen Vermögenskonten sind 0.',includes:'Transfers innerhalb desselben persönlichen Eigentumsbereichs zwischen Zahlungsverkehr und Vermögenskonten.',excludes:'Kursgewinne, Zinsen, Depotwertentwicklung, Transfers in Gemeinschaftsbereiche und Transfers zu anderen Personen.',interpretation:'Zeigt nur den planmäßigen Geldfluss in persönliches Vermögen, nicht die Rendite des Vermögens.',example:'300 € Giro → Depot und 50 € Depot → Giro ergeben +250 € Vermögensaufbau netto.'}, + sharedNetChange:{section:'Gemeinschaftsfinanzierung',title:'Nettoveränderung Gemeinschaftsvermögen',meaning:'Wie sich der gemeinsame Geldtopf durch externe wirtschaftliche Zu- und Abflüsse im Durchschnitt verändert.',formula:'Gemeinsame Einnahmen + tatsächliche externe Zuführungen − gemeinsame Kosten − externe Entnahmen.',includes:'Echte Einnahmen auf Gemeinschaftskonten, Transfers von außerhalb in den Gemeinschaftsbereich, echte gemeinsame Kosten und Transfers aus dem Gemeinschaftsbereich nach außen.',excludes:'Interne Umbuchungen zwischen Konten desselben Gemeinschaftsbereichs.',interpretation:'Positiv bedeutet, dass gemeinsames Vermögen aufgebaut wird. Negativ bedeutet, dass der Gemeinschaftsbereich netto Mittel verliert.',example:'500 € gemeinsame Einnahmen + 700 € externe Zuführung − 1.000 € Kosten − 100 € Entnahme = +100 € Nettoveränderung.'}, + sharedIncome:{section:'Gemeinschaftsfinanzierung',title:'Gemeinsame Einnahmen',meaning:'Echte Einnahmen, die direkt auf Konten eines Gemeinschaftsbereichs eingehen.',formula:'Summe der echten Eingangspositionen auf allen Gemeinschaftskonten mit demselben Eigentümerkreis.',includes:'Zum Beispiel Kindergeld oder andere echte gemeinsame Einnahmen.',excludes:'Persönliche Überweisungen aufs Gemeinschaftskonto. Diese sind externe Zuführungen, keine gemeinsamen Einnahmen.',interpretation:'Diese Einnahmen gehören dem gemeinsamen Topf und reduzieren den von Personen zu finanzierenden Kostenbedarf.',example:'500 € Kindergeld auf dem Gemeinschaftskonto = 500 € gemeinsame Einnahmen.'}, + sharedCost:{section:'Gemeinschaftsfinanzierung',title:'Gemeinsame Kosten',meaning:'Echte Ausgaben, die von Konten des Gemeinschaftsbereichs bezahlt werden.',formula:'Summe aller echten Ausgaben auf den Gemeinschaftskonten desselben Eigentümerkreises.',includes:'Kredit, Energie, Versicherungen oder andere als Ausgabe angelegte Positionen auf gemeinsamen Konten.',excludes:'Transfers innerhalb des Gemeinschaftsbereichs und Transfers auf gemeinsame Rücklagenkonten.',interpretation:'Zeigt den echten wirtschaftlichen Verbrauch des gemeinsamen Bereichs.',example:'900 € Kredit plus 300 € Energie ergeben 1.200 € gemeinsame Kosten.'}, + sharedNeed:{section:'Gemeinschaftsfinanzierung',title:'Kosten-Finanzierungsbedarf',meaning:'Der Teil der gemeinsamen Kosten, der nach echten gemeinsamen Einnahmen noch finanziert werden muss.',formula:'max(0, gemeinsame Kosten − gemeinsame Einnahmen).',includes:'Gemeinsame Kosten und echte gemeinsame Einnahmen.',excludes:'Bereits tatsächlich geleistete persönliche Transfers. Diese werden erst beim Ist-Beitrag verglichen.',interpretation:'Das ist der Soll-Bedarf des Gemeinschaftsbereichs, nicht zwingend der tatsächliche Transferbetrag und auch nicht der Kontostand.',example:'1.200 € gemeinsame Kosten minus 500 € gemeinsame Einnahmen ergeben 700 € Kosten-Finanzierungsbedarf.'}, + externalFunding:{section:'Gemeinschaftsfinanzierung',title:'Tatsächliche externe Zuführung',meaning:'Alle tatsächlichen Transfers, die von außerhalb des betrachteten Gemeinschaftsbereichs auf eines seiner Konten eingehen.',formula:'Summe aller Transfers mit Zielkonto im Gemeinschaftsbereich und Quellkonto außerhalb dieses Eigentumsbereichs.',includes:'Persönlich → gemeinsam, Transfers aus einem anderen Gemeinschaftsbereich und sonstige externe Kontotransfers, sofern Quelle außerhalb der Gruppe liegt.',excludes:'Echte gemeinsame Einnahmen und interne Umbuchungen zwischen Konten desselben Gemeinschaftsbereichs.',interpretation:'Die Zahl zeigt, wie viel Geld tatsächlich von außen in den gemeinsamen Topf verschoben wurde. Sie kann höher, niedriger oder gleich dem Kosten-Finanzierungsbedarf sein. Sie ist nicht automatisch identisch mit der Summe der persönlichen Ist-Beiträge, weil auch Zuführungen aus anderen nicht-persönlichen Bereichen enthalten sein können.',example:'350 € von Person A + 350 € von Person B + 100 € aus einem anderen Gemeinschaftsbereich = 800 € tatsächliche externe Zuführung. Persönliche Ist-Beiträge wären dabei nur 700 €.'}, + internalSharedTransfer:{section:'Gemeinschaftsfinanzierung',title:'Interne Umbuchungen',meaning:'Transfers zwischen Konten desselben Gemeinschaftsbereichs, also mit identischem gemeinsamen Eigentümerkreis.',formula:'Summe der Transfers, bei denen Quell- und Zielkonto beide zum selben Gemeinschaftsbereich gehören.',includes:'Zum Beispiel Gemeinschaftsgiro → gemeinsame Haus-Rücklage.',excludes:'Transfers von persönlichen Konten in die Gemeinschaft oder aus der Gemeinschaft heraus.',interpretation:'Verändert nur die Verteilung des gemeinsamen Geldes. Es entstehen weder Kosten noch externe Finanzierung.',example:'300 € Gemeinschaftsgiro → gemeinsame Rücklage sind 300 € interne Umbuchung, aber 0 € zusätzliche gemeinsame Kosten.'}, + externalWithdrawal:{section:'Gemeinschaftsfinanzierung',title:'Externe Entnahmen',meaning:'Transfers, die Geld aus dem Gemeinschaftsbereich auf ein Konto außerhalb dieses Bereichs verschieben.',formula:'Summe aller Transfers mit Quelle im Gemeinschaftsbereich und Ziel außerhalb des Bereichs.',includes:'Gemeinsam → persönlich oder gemeinsam → anderer Eigentumsbereich.',excludes:'Echte gemeinsame Ausgaben und interne gemeinsame Umbuchungen.',interpretation:'Senkt das Gemeinschaftsvermögen, ist aber nicht automatisch eine wirtschaftliche Ausgabe.',example:'100 € vom Gemeinschaftskonto auf ein persönliches Konto = 100 € externe Entnahme.'}, + costShare:{section:'Gemeinschaftsfinanzierung',title:'Kostenanteil',meaning:'Welcher Anteil der gemeinsamen echten Kosten einer Person aufgrund der Personenzuordnung der Ausgaben zugerechnet wird.',formula:'Persönlicher Bruttoanteil an gemeinsamen Kosten ÷ gesamte zugeordnete gemeinsame Kosten × 100.',includes:'Nur gemeinsame echte Ausgaben und deren Personenzuordnung.',excludes:'Persönliche Einkommen und tatsächliche Transfers.',interpretation:'Zeigt die Kostenverantwortung, nicht wer tatsächlich bezahlt hat.',example:'Bei 1.200 € gemeinsamen Kosten und 600 € zugeordnetem Anteil beträgt der Kostenanteil 50 %.'}, + targetFunding:{section:'Gemeinschaftsfinanzierung',title:'Soll-Finanzierung',meaning:'Wie viel des verbleibenden Kosten-Finanzierungsbedarfs eine Person gemäß ihrem Kostenanteil rechnerisch übernehmen sollte.',formula:'Kosten-Finanzierungsbedarf × Kostenanteil.',includes:'Den nach gemeinsamen Einnahmen verbleibenden Bedarf und den Kostenanteil der Person.',excludes:'Tatsächliche Überweisungen. Diese stehen als Ist-Beitrag daneben.',interpretation:'Ein Vergleichswert für die kostenbasierte Finanzierung, keine automatisch erzeugte Zahlung. Der daneben gezeigte Soll-% ist Soll-Finanzierung ÷ gesamter Kosten-Finanzierungsbedarf × 100 und entspricht bei dieser Verteilung dem Kostenanteil.',example:'700 € Bedarf × 50 % Kostenanteil = 350 € Soll-Finanzierung. 350 € ÷ 700 € = 50 % Soll-Finanzierungsanteil.'}, + actualContribution:{section:'Gemeinschaftsfinanzierung',title:'Ist-Beitrag',meaning:'Wie viel eine Person tatsächlich aus ihren persönlichen Konten in den Gemeinschaftsbereich überwiesen hat.',formula:'Summe der persönlichen → gemeinsamen Transfers, zugerechnet nach Eigentümer des persönlichen Quellkontos.',includes:'Nur Transfers von persönlichen Quellkonten in den betrachteten Gemeinschaftsbereich.',excludes:'Gemeinsame Einnahmen, Transfers aus anderen Gemeinschaften und interne Umbuchungen.',interpretation:'Zeigt die tatsächliche persönliche Finanzierung. Der Prozentwert daneben zeigt den Anteil dieser Person an allen persönlichen Ist-Beiträgen.',example:'Person A überweist 400 €, Person B 300 €. A hat 400 € Ist-Beitrag und 57,1 % der persönlichen Ist-Beiträge.'}, + needCoverage:{section:'Gemeinschaftsfinanzierung',title:'Anteil am Bedarf',meaning:'Welchen Anteil des gesamten verbleibenden Kosten-Finanzierungsbedarfs die tatsächliche persönliche Einzahlung einer Person deckt.',formula:'Ist-Beitrag der Person ÷ Kosten-Finanzierungsbedarf × 100.',includes:'Tatsächlichen persönlichen Ist-Beitrag und den verbleibenden gemeinsamen Bedarf.',excludes:'Gemeinsame Einnahmen, da diese bereits vor Ermittlung des Bedarfs abgezogen wurden.',interpretation:'50 % bedeutet: Diese Person deckt mit ihrer tatsächlichen Zahlung die Hälfte des noch privat zu finanzierenden Bedarfs.',example:'350 € Ist-Beitrag bei 700 € Bedarf = 50 % Anteil am Bedarf.'}, + fundingDifference:{section:'Gemeinschaftsfinanzierung',title:'Differenz Ist − Soll',meaning:'Abweichung zwischen tatsächlich gezahltem persönlichem Beitrag und kostenbasierter Soll-Finanzierung.',formula:'Ist-Beitrag − Soll-Finanzierung.',includes:'Nur die beiden direkt verglichenen Werte Ist und Soll.',excludes:'Gemeinsame Einnahmen sind bereits im Bedarf berücksichtigt und werden nicht nochmals addiert.',interpretation:'Positiv bedeutet mehr gezahlt als Soll, negativ weniger, 0 entspricht dem kostenbasierten Soll.',example:'400 € Ist minus 350 € Soll = +50 € Differenz.'}, + householdBurden:{section:'Einkommensvergleich',title:'Belastungsquote Haushalt',meaning:'Welcher Anteil der gesamten persönlichen Einkommen der Eigentümer nötig wäre, um den nach gemeinsamen Einnahmen verbleibenden Gemeinschaftsbedarf zu decken.',formula:'Verbleibender Bedarf ÷ Summe der persönlichen Einkommen × 100.',includes:'Persönliche Einkommen der Gemeinschaftseigentümer und den verbleibenden Bedarf.',excludes:'Gemeinsame Einnahmen als persönliches Einkommen. Diese wurden bereits zur Bedarfsreduktion verwendet.',interpretation:'Die Quote beschreibt die relative Haushaltsbelastung. Bei proportionaler Einkommensverteilung entspricht sie auch dem relativen Einkommensanteil, den jede Person für die Gemeinschaft aufbringen müsste.',example:'700 € Bedarf bei 5.500 € persönlichen Einkommen = 12,7 % Belastungsquote.'}, + sharedIncomeCoverage:{section:'Einkommensvergleich',title:'Deckung gemeinsame Einnahmen',meaning:'Der Teil der gemeinsamen Kosten, der direkt durch echte gemeinsame Einnahmen finanziert wird.',formula:'min(gemeinsame Einnahmen, gemeinsame Kosten).',includes:'Echte gemeinsame Einnahmen bis maximal zur Höhe der gemeinsamen Kosten.',excludes:'Persönliche Transfers und Einnahmenüberschüsse oberhalb der Kosten.',interpretation:'Diese Deckung muss nicht aus persönlichen Einkommen finanziert werden.',example:'500 € gemeinsame Einnahmen bei 1.200 € Kosten decken 500 €; verbleibender Bedarf 700 €.'}, + remainingNeed:{section:'Einkommensvergleich',title:'Verbleibender Bedarf',meaning:'Gemeinsame Kosten, die nach Nutzung gemeinsamer Einnahmen noch durch persönliche oder andere externe Finanzierung gedeckt werden müssen.',formula:'max(0, gemeinsame Kosten − gemeinsame Einnahmen).',includes:'Gemeinsame Kosten und gemeinsame Einnahmen.',excludes:'Die tatsächlichen persönlichen Beiträge. Diese werden anschließend mit dem Soll verglichen.',interpretation:'Ist der Wert 0 €, reichen gemeinsame Einnahmen aus, um die gemeinsamen Kosten vollständig zu decken.',example:'1.200 € Kosten − 500 € gemeinsame Einnahmen = 700 € verbleibender Bedarf.'}, + personalIncomeTotal:{section:'Einkommensvergleich',title:'Persönliche Einkommen gesamt',meaning:'Summe der echten persönlichen Einkommen aller Eigentümer des betrachteten Gemeinschaftsbereichs.',formula:'Summe der echten Eingänge auf persönlichen Konten der Eigentümer.',includes:'Persönliche echte Einnahmen aller Gemeinschaftseigentümer.',excludes:'Gemeinsame Einnahmen und Transfers.',interpretation:'Dient als Basis für die einkommensproportionale Soll-Verteilung.',example:'3.000 € + 2.500 € persönliche Einkommen = 5.500 € persönliche Einkommen gesamt.'}, + incomeShare:{section:'Einkommensvergleich',title:'Einkommensanteil / Soll-%',meaning:'Anteil einer Person an der Summe der persönlichen Einkommen der Gemeinschaftseigentümer.',formula:'Persönliches Einkommen der Person ÷ persönliche Einkommen gesamt × 100.',includes:'Nur persönliche echte Einkommen.',excludes:'Gemeinsame Einnahmen und Transfers.',interpretation:'Dieser Prozentsatz wird in der einkommensbasierten Vergleichssicht als Soll-% für den verbleibenden Bedarf verwendet.',example:'3.000 € von 5.500 € = 54,5 % Einkommensanteil und damit 54,5 % Soll-%.'}, + incomeTarget:{section:'Einkommensvergleich',title:'Soll nach Einkommen',meaning:'Hypothetischer persönlicher Finanzierungsbeitrag, wenn der verbleibende Gemeinschaftsbedarf proportional zu den persönlichen Einkommen verteilt würde.',formula:'Verbleibender Bedarf × Einkommensanteil.',includes:'Verbleibenden Bedarf und persönlichen Einkommensanteil.',excludes:'Tatsächliche Zahlungen. Es ist eine Vergleichsrechnung.',interpretation:'Dient als Fairnessvergleich zum Ist-Beitrag und verändert keine Buchung.',example:'700 € Bedarf × 54,5 % = 381,82 € Soll nach Einkommen.'}, + incomeBurden:{section:'Einkommensvergleich',title:'Belastung vom Einkommen',meaning:'Welcher Anteil des persönlichen Einkommens durch das einkommensbasierte Soll gebunden wäre.',formula:'Soll nach Einkommen ÷ persönliches Einkommen × 100.',includes:'Persönliches Einkommen und einkommensbasiertes Soll.',excludes:'Andere persönliche Kosten außerhalb dieser Vergleichsrechnung.',interpretation:'Bei streng proportionaler Verteilung ist diese Quote für alle Personen mit Einkommen gleich der Haushalts-Belastungsquote.',example:'381,82 € Soll bei 3.000 € Einkommen = 12,7 % Belastung.'}, + actualContributionShare:{section:'Einkommensvergleich',title:'Ist-Beitrag / Ist-%',meaning:'Tatsächlicher persönlicher Beitrag und dessen Anteil an allen persönlichen Einzahlungen in den Gemeinschaftsbereich.',formula:'Ist-% = Ist-Beitrag der Person ÷ Summe aller persönlichen Ist-Beiträge × 100.',includes:'Persönliche → gemeinsame Transfers.',excludes:'Gemeinsame Einnahmen und externe Zuführungen aus nicht persönlichen Bereichen.',interpretation:'Zeigt, wie die tatsächliche private Finanzierung zwischen den Personen verteilt ist.',example:'400 € von insgesamt 700 € persönlichen Einzahlungen = 57,1 % Ist-%.'}, + incomeDeviation:{section:'Einkommensvergleich',title:'Abweichung Soll / Ist',meaning:'Vergleicht den tatsächlichen persönlichen Beitrag mit dem einkommensbasierten Soll.',formula:'Betragsabweichung = Ist-Beitrag − Soll nach Einkommen. Prozentpunktabweichung = Ist-% − Soll-% (Einkommensanteil).',includes:'Einkommensbasiertes Soll und tatsächlichen persönlichen Beitrag.',excludes:'Kostenbasierte Soll-Finanzierung aus dem anderen Gemeinschaftsblock.',interpretation:'Positiv bedeutet über dem einkommensbasierten Soll, negativ darunter.',example:'400 € Ist − 381,82 € Soll = +18,18 €. 57,1 % Ist − 54,5 % Soll = +2,6 Prozentpunkte.'}, + categoryShare:{section:'Kategorien',title:'Kategorieanteil %',meaning:'Wie groß der Anteil einer Kategorie an der Gesamtsumme des jeweiligen Statistikblocks ist.',formula:'Ø-Monatswert der Kategorie ÷ Ø-Monats-Gesamtsumme des Blocks × 100.',includes:'Nur Positionen, die durch Konto-, Personen- und Typfilter in diesem Block enthalten sind.',excludes:'Positionen außerhalb des Blocks. Bei Kosten sind Transfers ausgeschlossen, bei Transfers sind echte Kosten ausgeschlossen.',interpretation:'Die Prozentwerte sind nur innerhalb desselben Blocks vergleichbar.',example:'600 € Kategorie bei 2.400 € Gesamtkosten = 25 % Kategorieanteil.'}, + uncategorized:{section:'Kategorien',title:'Ohne Kategorie',meaning:'Anzahl echter Eingangs- und Ausgangspositionen der aktuellen Auswahl, denen keine Kategorie zugewiesen ist.',formula:'Anzahl passender Einnahmen und Ausgaben mit leerer categoryId.',includes:'Echte Einnahmen und echte Ausgaben der aktuellen Auswahl.',excludes:'Transfers, damit der Wert unabhängig vom Transferdetail-Schalter bleibt.',interpretation:'Ein Datenqualitätsindikator. Je niedriger, desto vollständiger ist die Kategorisierung.',example:'2 Ausgaben und 1 Eingang ohne Kategorie ergeben den Wert 3.'}, + transferType:{section:'Transfers',title:'Transfer- und Finanzierungsart',meaning:'Automatische fachliche Einordnung eines Transfers anhand von Eigentum und Kontofunktion von Quell- und Zielkonto.',formula:'Regelbasiert, z. B. persönlich → gemeinsam = Gemeinschaftsfinanzierung; Giro → eigenes Depot = Zuführung Investment; gleiche Eigentumsdomäne = interne Umbuchung oder Vermögensverschiebung.',includes:'Alle Transfers der aktuellen Auswahl.',excludes:'Echte Einnahmen und Ausgaben.',interpretation:'Die Klassifikation erklärt den Zweck des Geldflusses, ohne ihn als Kosten oder Einnahme umzudeuten.',example:'Böhmi Giro → Gemeinschaftskonto wird als Gemeinschaftsfinanzierung klassifiziert; Gemeinschaftsgiro → gemeinsame Rücklage als Zuführung gemeinsame Rücklage.'} +}; + +function StatsTermExplanation({definition}){ + return

Was bedeutet die Zahl?
{definition.meaning}

Berechnung
{definition.formula}

Enthalten
{definition.includes}

Nicht enthalten
{definition.excludes}

Aussage / Interpretation
{definition.interpretation}

Beispiel
{definition.example}

; +} +function StatsTerm({term,label}){ + const definition=STATS_GLOSSARY[term]; + if(!definition)return {label||term}; + return {label||definition.title}; +} +function statTermKeyForLabel(label=''){ + const value=String(label).replace(/^[+−=\-\s]+/,'').toLocaleLowerCase('de-DE'); + const pairs=[ + ['wirtschaftlicher saldo','economicBalance'],['wirtschaftlich verfügbar','economicAvailable'],['liquiditäts-nettofluss','accountNetFlow'],['nettofluss','accountNetFlow'],['kostenquote','costRatio'],['rücklagenbedarf periodische kosten','periodicReserve'],['teuerster monat','expensiveMonth'],['günstigster monat','cheapMonth'],['ohne kategorie','uncategorized'],['echte eingänge','realIncome'],['eigene kontoeingänge','personalIncome'],['echte ausgaben','realExpense'],['echte kosten','realExpense'],['transfers rein','transferIn'],['transfers raus','transferOut'],['brutto-kostenanteil','grossCostShare'],['gemeinsame deckung','sharedCoverage'],['netto-kostenlast','netCost'],['soll gemeinschaftsfinanzierung','targetFunding'],['ist gemeinschaftsfinanzierung','actualContribution'],['vermögensaufbau netto','assetBuild'],['kosten-finanzierungsbedarf','sharedNeed'],['gemeinsame einnahmen','sharedIncome'],['gemeinsame kosten','sharedCost'],['tatsächliche externe zuführung','externalFunding'],['kostenanteil','costShare'],['soll-finanzierung','targetFunding'],['ist-beitrag','actualContribution'],['anteil am bedarf','needCoverage'],['differenz','fundingDifference'],['belastungsquote','householdBurden'],['verbleibender finanzierungsbedarf','remainingNeed'],['verbleibender bedarf','remainingNeed'],['persönliche einkommen','personalIncomeTotal'],['einkommensanteil','incomeShare'],['soll nach einkommen','incomeTarget'],['belastung vom einkommen','incomeBurden'],['abweichung soll / ist','incomeDeviation'] + ]; + return pairs.find(([needle])=>value.includes(needle))?.[1]||null; +} +function StatsGlossaryButton(){ + const [open,setOpen]=useState(false); + const groups=[...new Set(Object.values(STATS_GLOSSARY).map(x=>x.section))]; + return <>{open&&setOpen(false)}>

Hier sind sämtliche fachlichen Kennzahlen der Statistik erklärt. Jede Erklärung nennt Bedeutung, Formel, Ein- und Ausschlüsse, Interpretation und ein Beispiel.

{groups.map(group=>

{group}

{Object.entries(STATS_GLOSSARY).filter(([,def])=>def.section===group).map(([key,def])=>
{def.title}
)}
)}
}; +} + function StatsHelpModal({title,onClose,children}){ useModalScrollLock(); return
e.target===e.currentTarget&&onClose()}>
STATISTIK ERKLÄRT

{title}

{children}
; @@ -576,19 +650,22 @@ function StatItemList({data,items,empty='Keine beitragenden Positionen.',amountF })}; } function StatFormulaRows({rows}){ - return
{rows.map((row,index)=>
{row.label}{typeof row.value==='number'?formatMoney(row.value):row.value}{row.note&&{row.note}}
)}
; + return
{rows.map((row,index)=>{const term=row.term||statTermKeyForLabel(row.label);return
{term?:{row.label}}{typeof row.value==='number'?formatMoney(row.value):row.value}{row.note&&{row.note}}
})}
; } function StatMetricCard({label,value,displayValue,tone,featured=false,hint,helpTitle,help,children}){ - return
{label}{help&&{help}}
{displayValue??formatMoney(value)}{hint&&{hint}}{children&&
Zusammensetzung
{children}
}
; + const autoTerm=statTermKeyForLabel(label); + const definition=autoTerm?STATS_GLOSSARY[autoTerm]:null; + const fullHelp=definition?<>{help&&
Für diese konkrete Karte{help}
}:help; + return
{label}{fullHelp&&{fullHelp}}
{displayValue??formatMoney(value)}{hint&&{hint}}{children&&
Zusammensetzung
{children}
}
; } function StatsMethodPanel({year}){ - return

Die Statistik ist eine Planungs- und Durchschnittssicht für das gewählte Kalenderjahr. Sie ist keine Liste tatsächlich gebuchter Bankumsätze.

Ø / Monat bedeutet immer: im Jahr eingeplanter Betrag ÷ 12. Dadurch werden jährliche, vierteljährliche, halbjährliche und einmalige Positionen vergleichbar.

Transfers bleiben wirtschaftlich getrennt von Einnahmen und Kosten. In einer einzelnen Kontosicht gehören sie trotzdem zum Liquiditäts-Nettofluss.

}>

Alle Ø-Werte beziehen sich auf {year}. Einmalige Positionen zählen genau einmal im Zieljahr. Periodische Positionen mit Fälligkeitsmonat werden in ihren echten Monaten eingeplant.

WirtschaftlichEchte Einnahmen minus echte Ausgaben. Transfers zählen nicht als Gewinn oder Kosten.
PersonenAusgaben werden nach den Personen der Bewegung geteilt. Persönliches Einkommen folgt in der Gesamtansicht dem Eigentümer des persönlichen Kontos.
GemeinschaftGemeinsame Einnahmen decken zuerst gemeinsame Kosten und werden niemals zu persönlichem Einkommen.
LiquiditätBei einem einzelnen Konto werden Transfers rein und raus im Nettofluss vollständig berücksichtigt.
; + return

Die Statistik ist eine Planungs- und Durchschnittssicht für das gewählte Kalenderjahr. Sie ist keine Liste tatsächlich gebuchter Bankumsätze.

Ø / Monat bedeutet immer: im Jahr eingeplanter Betrag ÷ 12. Dadurch werden jährliche, vierteljährliche, halbjährliche und einmalige Positionen vergleichbar.

Transfers bleiben wirtschaftlich getrennt von Einnahmen und Kosten. In einer einzelnen Kontosicht gehören sie trotzdem zum Liquiditäts-Nettofluss.

}>

Alle Ø-Werte beziehen sich auf {year}. Einmalige Positionen zählen genau einmal im Zieljahr. Periodische Positionen mit Fälligkeitsmonat werden in ihren echten Monaten eingeplant. Jedes kleine i öffnet eine vollständige Definition mit Formel, Ein- und Ausschlüssen, Interpretation und Beispiel.

WirtschaftlichEchte Einnahmen minus echte Ausgaben. Transfers zählen nicht als Gewinn oder Kosten.
PersonenAusgaben werden nach den Personen der Bewegung geteilt. Persönliches Einkommen folgt in der Gesamtansicht dem Eigentümer des persönlichen Kontos.
GemeinschaftGemeinsame Einnahmen decken zuerst gemeinsame Kosten und werden niemals zu persönlichem Einkommen.
LiquiditätBei einem einzelnen Konto werden Transfers rein und raus im Nettofluss vollständig berücksichtigt.
; } function PersonFlowOverview({stats,year}){ const help=<>

Wirtschaftlich verfügbar beantwortet: Was bleibt nach der persönlichen Kostenlast übrig? Liquid verfügbar beantwortet dagegen: Was bleibt auf persönlichen Zahlungsverkehrskonten nach echten Kontobewegungen übrig?

Diese beiden Werte dürfen unterschiedlich sein. Eine Überweisung ins Depot oder aufs Gemeinschaftskonto ist keine Ausgabe, reduziert aber die liquide Verfügbarkeit.

; - if(!stats.isAll)return

Ø-Monatsfluss {year} auf dem ausgewählten Konto. Transfers sind immer im Nettofluss enthalten; der Schalter blendet nur Transferdetails aus.

=0?'good-text':'bad-text'}`}>{stats.totals.net>=0?'+':''}{formatMoney(stats.totals.net)} Ø / Monat
{stats.rows.length===0?:
{stats.rows.map(row=>
{row.name}Nettofluss auf diesem Konto
=0?'good-text':'bad-text'}>{row.net>=0?'+':''}{formatMoney(row.net)}
Echte Eingänge+{formatMoney(row.income)}
Echte Ausgaben−{formatMoney(row.expense)}
{stats.includeTransfers&&<>
Transfers rein+{formatMoney(row.transferIn)}
Transfers raus−{formatMoney(row.transferOut)}
}
Rechenweg
)}
}
; - return

Wirtschaftliche Kosten und persönliche Liquidität als Ø-Monatswerte {year}. Transfers verändern Liquidität, bleiben aber außerhalb der Kostenstatistik.

=0?'good-text':'bad-text'}`}>{stats.totals.liquidAvailable>=0?'+':''}{formatMoney(stats.totals.liquidAvailable)} Ø / Monat liquid
{stats.rows.length===0?:
{stats.rows.map(row=>
{row.name}Liquid verfügbar auf persönlichen Zahlungsverkehrskonten
=0?'good-text':'bad-text'}>{row.liquidAvailable>=0?'+':''}{formatMoney(row.liquidAvailable)}
Wirtschaftlich verfügbar=0?'good-text':'bad-text'}>{row.economicAvailable>=0?'+':''}{formatMoney(row.economicAvailable)}
Brutto-Kostenanteil−{formatMoney(row.expenseShare)}
Gemeinsame Deckung+{formatMoney(row.sharedCoverage)}
Netto-Kostenlast−{formatMoney(row.netCost)}
Eigene Kontoeingänge+{formatMoney(row.personalIncome)}
{stats.includeTransfers&&<>
Liquid-Transfers rein+{formatMoney(row.liquidTransferIn)}
Liquid-Transfers raus−{formatMoney(row.liquidTransferOut)}
Gemeinschaft finanziert{formatMoney(row.sharedFundingActual)}
Vermögensaufbau netto=0?'good-text':'bad-text'}>{row.assetBuild>=0?'+':''}{formatMoney(row.assetBuild)}
}
Rechenweg
)}
}
; + if(!stats.isAll)return

Ø-Monatsfluss {year} auf dem ausgewählten Konto. Transfers sind immer im Nettofluss enthalten; der Schalter blendet nur Transferdetails aus.

=0?'good-text':'bad-text'}`}>{stats.totals.net>=0?'+':''}{formatMoney(stats.totals.net)} Ø / Monat
{stats.rows.length===0?:
{stats.rows.map(row=>
{row.name}
=0?'good-text':'bad-text'}>{row.net>=0?'+':''}{formatMoney(row.net)}
+{formatMoney(row.income)}
−{formatMoney(row.expense)}
{stats.includeTransfers&&<>
+{formatMoney(row.transferIn)}
−{formatMoney(row.transferOut)}
}
Rechenweg
)}
}
; + return

Wirtschaftliche Kosten und persönliche Liquidität als Ø-Monatswerte {year}. Transfers verändern Liquidität, bleiben aber außerhalb der Kostenstatistik.

=0?'good-text':'bad-text'}`}>{stats.totals.liquidAvailable>=0?'+':''}{formatMoney(stats.totals.liquidAvailable)} Ø / Monat liquid
{stats.rows.length===0?:
{stats.rows.map(row=>
{row.name}
=0?'good-text':'bad-text'}>{row.liquidAvailable>=0?'+':''}{formatMoney(row.liquidAvailable)}
=0?'good-text':'bad-text'}>{row.economicAvailable>=0?'+':''}{formatMoney(row.economicAvailable)}
−{formatMoney(row.expenseShare)}
+{formatMoney(row.sharedCoverage)}
−{formatMoney(row.netCost)}
+{formatMoney(row.personalIncome)}
{stats.includeTransfers&&<>
+{formatMoney(row.liquidTransferIn)}
−{formatMoney(row.liquidTransferOut)}
{formatMoney(row.sharedFundingActual)}
=0?'good-text':'bad-text'}>{row.assetBuild>=0?'+':''}{formatMoney(row.assetBuild)}
}
Rechenweg
)}
}
; } function SharedFundingOverview({data,personId='all',showTransfers=true,year=new Date().getFullYear()}){ @@ -596,7 +673,7 @@ function SharedFundingOverview({data,personId='all',showTransfers=true,year=new if(!stats.groups.length)return null; const percent=value=>value==null?'–':`${value.toFixed(1).replace('.',',')} %`; const help=<>

Kosten-Finanzierungsbedarf = gemeinsame Kosten − gemeinsame Einnahmen, mindestens 0.

Soll-Finanzierung verteilt diesen Bedarf im Verhältnis der tatsächlich zugeordneten gemeinsamen Kosten. Ist-Beitrag stammt aus Transfers von persönlichen Konten in diesen Gemeinschaftsbereich und wird dem Eigentümer des Quellkontos zugerechnet.

Nettoveränderung Gemeinschaftsvermögen = gemeinsame Einnahmen + externe Zuführungen − gemeinsame Kosten − externe Entnahmen. Interne Umbuchungen zwischen gemeinsamen Konten desselben Eigentümerkreises fallen heraus.

; - return

Ø-Monatswerte {year}. Gemeinsame Einnahmen bleiben im gemeinsamen Topf und reduzieren den Finanzierungsbedarf.

{stats.groups.map(group=>
{group.accounts.map(a=>a.name).join(' + ')}{group.ownerPersonIds.map(id=>personName(data,id)).join(', ')}
Nettoveränderung Gemeinschaftsvermögen=0?'good-text':'bad-text'}>{group.netAssetChange>=0?'+':''}{formatMoney(group.netAssetChange)} Ø / Monat
Gemeinsame Einnahmen+{formatMoney(group.income)}
Gemeinsame Kosten−{formatMoney(group.expense)}
Kosten-Finanzierungsbedarf{formatMoney(group.costNeed)}
{showTransfers&&
Tatsächliche externe Zuführung{formatMoney(group.fundingIn)}
}
{showTransfers&&group.internalTransfers>0&&
Interne Umbuchungen innerhalb dieses Gemeinschaftsbereichs: {formatMoney(group.internalTransfers)} Ø / Monat. Sie sind weder Kosten noch externe Finanzierung.
}
{group.personRows.map(row=>
{row.name}
Kostenanteil{formatMoney(row.grossCost)} · {percent(row.costShare)}
Soll-Finanzierung{formatMoney(row.targetFunding)} · {percent(row.targetFundingShare)}
{showTransfers&&<>
Ist-Beitrag{formatMoney(row.actualFunding)} · {percent(row.actualFundingShare)}
Anteil am Bedarf{percent(row.needCoverageShare)}
Differenz=0?'good-text':'bad-text'}>{row.difference>=0?'+':''}{formatMoney(row.difference)}
}
)}
Zusammensetzung des Gemeinschaftsbereichs

Gemeinsame Einnahmen

Gemeinsame Kosten

{showTransfers&&<>

Externe Zuführungen

Interne Umbuchungen

Externe Entnahmen

}
{showTransfers&&
Ist-Beitrag % ist der Anteil an allen persönlichen Einzahlungen in den Gemeinschaftsbereich. Anteil am Bedarf zeigt, wie viel des verbleibenden Kostenbedarfs diese Person tatsächlich deckt.
}{!showTransfers&&
Transferdetails sind ausgeblendet. Kostenanteil und Soll bleiben sichtbar; tatsächliche Beiträge nicht.
}
)}
; + return

Ø-Monatswerte {year}. Gemeinsame Einnahmen bleiben im gemeinsamen Topf und reduzieren den Finanzierungsbedarf.

{stats.groups.map(group=>
{group.accounts.map(a=>a.name).join(' + ')}{group.ownerPersonIds.map(id=>personName(data,id)).join(', ')}
=0?'good-text':'bad-text'}>{group.netAssetChange>=0?'+':''}{formatMoney(group.netAssetChange)} Ø / Monat
+{formatMoney(group.income)}
−{formatMoney(group.expense)}
{formatMoney(group.costNeed)}
{showTransfers&&
{formatMoney(group.fundingIn)}
}
{showTransfers&&group.internalTransfers>0&&
Interne Umbuchungen innerhalb dieses Gemeinschaftsbereichs: {formatMoney(group.internalTransfers)} Ø / Monat. Sie sind weder Kosten noch externe Finanzierung.
}
{group.personRows.map(row=>
{row.name}
{formatMoney(row.grossCost)} · {percent(row.costShare)}
{formatMoney(row.targetFunding)} · {percent(row.targetFundingShare)}
{showTransfers&&<>
{formatMoney(row.actualFunding)} · {percent(row.actualFundingShare)}
{percent(row.needCoverageShare)}
=0?'good-text':'bad-text'}>{row.difference>=0?'+':''}{formatMoney(row.difference)}
}
)}
Zusammensetzung des Gemeinschaftsbereichs

{showTransfers&&<>

}
{showTransfers&&
Ist-Beitrag % ist der Anteil an allen persönlichen Einzahlungen in den Gemeinschaftsbereich. Anteil am Bedarf zeigt, wie viel des verbleibenden Kostenbedarfs diese Person tatsächlich deckt.
}{!showTransfers&&
Transferdetails sind ausgeblendet. Kostenanteil und Soll bleiben sichtbar; tatsächliche Beiträge nicht.
}
)}
; } function SharedIncomeBurdenOverview({data,personId='all',showTransfers=true,year=new Date().getFullYear()}){ @@ -605,7 +682,7 @@ function SharedIncomeBurdenOverview({data,personId='all',showTransfers=true,year const percent=value=>value==null?'–':`${value.toFixed(1).replace('.',',')} %`; const percentPoints=value=>value==null?'–':`${value>=0?'+':''}${value.toFixed(1).replace('.',',')} %-Pkt.`; const help=<>

Diese Sicht ist ein Fairnessvergleich, keine automatische Zahlungsregel.

Zuerst werden gemeinsame Einnahmen von gemeinsamen Kosten abgezogen. Der verbleibende Bedarf wird dann proportional zu den persönlichen Einkommen der Eigentümer verteilt. Deshalb entspricht der Soll-% dem Einkommensanteil.

Die Belastungsquote des Haushalts ist verbleibender Bedarf ÷ Summe der persönlichen Einkommen. Bei proportionaler Verteilung ist diese Quote für alle Personen mit Einkommen gleich.

; - return

Ø-Monatswerte {year}. Diese Vergleichssicht zeigt, wie der verbleibende Gemeinschaftsbedarf bei einkommensproportionaler Finanzierung verteilt wäre.

{stats.groups.map(group=>{const personalIncomeItems=(data.incomes||[]).filter(item=>{const account=data.accounts.find(a=>a.id===item.accountId);return account?.ownership==='personal'&&ownerIdsOf(account).some(id=>group.ownerPersonIds.includes(id));}).map(item=>({...item,statAmount:statMonthlyAverage(item,year),statAnnual:plannedAmountForYear(item,year)})).filter(item=>item.statAnnual);return
{group.accounts.map(a=>a.name).join(' + ')}{group.ownerPersonIds.map(id=>personName(data,id)).join(', ')}
Belastungsquote Haushalt{percent(group.householdBurdenRate)}
Gemeinsame Kosten−{formatMoney(group.expense)}
Deckung gemeinsame Einnahmen+{formatMoney(group.sharedIncomeCoverage)}
Verbleibender Bedarf{formatMoney(group.costNeed)}
Persönliche Einkommen gesamt{formatMoney(group.personalIncomeTotal)}
{group.sharedIncomeSurplus>0&&
Gemeinsame Einnahmen übersteigen die gemeinsamen Kosten um {formatMoney(group.sharedIncomeSurplus)} Ø / Monat. Es ist keine persönliche Finanzierung nötig.
}{!group.incomeAllocationAvailable?
Keine persönlichen Einnahmen auf persönlichen Konten vorhanden. Eine einkommensbasierte Verteilung kann deshalb nicht berechnet werden.
:
{group.incomeBurdenRows.map(row=>
{row.name}
Persönliches Einkommen{formatMoney(row.personalIncome)}
Einkommensanteil / Soll-%{percent(row.incomeShare==null?null:row.incomeShare*100)}
Soll nach Einkommen{row.incomeBasedFunding==null?'–':formatMoney(row.incomeBasedFunding)}
Belastung vom Einkommen{percent(row.burdenRate)}
{showTransfers&&<>
Ist-Beitrag / Ist-%{formatMoney(row.actualFunding)} · {percent(row.actualFundingShare==null?null:row.actualFundingShare*100)}
Abweichung Soll / Ist=0?'good-text':'bad-text'}>{row.difference==null?'–':`${row.difference>=0?'+':''}${formatMoney(row.difference)} · ${percentPoints(row.shareDifferencePoints)}`}
}
)}
}
Rechenweg und Einkommensquellen

Persönliche Einkommensquellen

{showTransfers?'Soll-% entspricht dem Einkommensanteil. Ist-% zeigt den Anteil an allen tatsächlichen persönlichen Einzahlungen. Die %-Punkt-Abweichung zeigt Über- oder Unterbeitrag relativ zum einkommensbasierten Soll. ':'Transferdetails sind ausgeblendet. '}Diese Sicht verändert keine Bewegungen oder Kontosalden.
})}
; + return

Ø-Monatswerte {year}. Diese Vergleichssicht zeigt, wie der verbleibende Gemeinschaftsbedarf bei einkommensproportionaler Finanzierung verteilt wäre.

{stats.groups.map(group=>{const personalIncomeItems=(data.incomes||[]).filter(item=>{const account=data.accounts.find(a=>a.id===item.accountId);return account?.ownership==='personal'&&ownerIdsOf(account).some(id=>group.ownerPersonIds.includes(id));}).map(item=>({...item,statAmount:statMonthlyAverage(item,year),statAnnual:plannedAmountForYear(item,year)})).filter(item=>item.statAnnual);return
{group.accounts.map(a=>a.name).join(' + ')}{group.ownerPersonIds.map(id=>personName(data,id)).join(', ')}
{percent(group.householdBurdenRate)}
−{formatMoney(group.expense)}
+{formatMoney(group.sharedIncomeCoverage)}
{formatMoney(group.costNeed)}
{formatMoney(group.personalIncomeTotal)}
{group.sharedIncomeSurplus>0&&
Gemeinsame Einnahmen übersteigen die gemeinsamen Kosten um {formatMoney(group.sharedIncomeSurplus)} Ø / Monat. Es ist keine persönliche Finanzierung nötig.
}{!group.incomeAllocationAvailable?
Keine persönlichen Einnahmen auf persönlichen Konten vorhanden. Eine einkommensbasierte Verteilung kann deshalb nicht berechnet werden.
:
{group.incomeBurdenRows.map(row=>
{row.name}
{formatMoney(row.personalIncome)}
{percent(row.incomeShare==null?null:row.incomeShare*100)}
{row.incomeBasedFunding==null?'–':formatMoney(row.incomeBasedFunding)}
{percent(row.burdenRate)}
{showTransfers&&<>
{formatMoney(row.actualFunding)} · {percent(row.actualFundingShare==null?null:row.actualFundingShare*100)}
=0?'good-text':'bad-text'}>{row.difference==null?'–':`${row.difference>=0?'+':''}${formatMoney(row.difference)} · ${percentPoints(row.shareDifferencePoints)}`}
}
)}
}
Rechenweg und Einkommensquellen

{showTransfers?'Soll-% entspricht dem Einkommensanteil. Ist-% zeigt den Anteil an allen tatsächlichen persönlichen Einzahlungen. Die %-Punkt-Abweichung zeigt Über- oder Unterbeitrag relativ zum einkommensbasierten Soll. ':'Transferdetails sind ausgeblendet. '}Diese Sicht verändert keine Bewegungen oder Kontosalden.
})}
; } function dashboardKpis(data, year = new Date().getFullYear()) { @@ -646,6 +723,7 @@ function buildWarnings(data, includeTransfers = true){ for(const account of data.accounts){const f=annualForecast(data,account.id,year,'all',includeTransfers);const negatives=f.rows.slice(currentMonth).filter(r=>r.balance<0);if(negatives.length){const worst=negatives.reduce((a,b)=>b.balanceisPeriodic(x)&&occursInMonth(x,m,year));if(due.length>=3){const total=due.reduce((sum,x)=>sum+effectiveAmount(x,year,m),0);warnings.push({kind:'warning',title:`${due.length} periodische Ausgaben im ${months[m]}`,text:`Zusammen ${formatMoney(total)}. Dieser Monat bündelt mehrere größere Fälligkeiten.`,sort:1});}} for(const [type,items] of [['Eingang',data.incomes||[]],['Ausgang',data.expenses||[]]])for(const item of items){for(const change of item.amountChanges||[]){const d=new Date(`${change.effectiveFrom}T12:00:00`);if(d>=now&&d<=horizon)warnings.push({kind:'info',title:`${item.name}: Betrag ändert sich`,text:`${type} auf ${accountName(data,item.accountId)} ab ${formatDate(change.effectiveFrom)}: ${formatMoney(change.amount)}.`,sort:2,date:d});}if(item.endDate){const d=new Date(`${item.endDate}T12:00:00`);if(d>=now&&d<=horizon)warnings.push({kind:'info',title:`${item.name} läuft aus`,text:`${type} auf ${accountName(data,item.accountId)} endet am ${formatDate(item.endDate)}.`,sort:3,date:d});}} + for(const item of data.transfers||[]){for(const change of item.amountChanges||[]){const d=new Date(`${change.effectiveFrom}T12:00:00`);if(d>=now&&d<=horizon)warnings.push({kind:'info',title:`${item.name}: Transferbetrag ändert sich`,text:`${accountName(data,item.fromAccountId)} → ${accountName(data,item.toAccountId)} ab ${formatDate(change.effectiveFrom)}: ${formatMoney(change.amount)}.`,sort:2,date:d});}} return warnings.sort((a,b)=>a.sort-b.sort||(a.date?.getTime()||0)-(b.date?.getTime()||0)).slice(0,10); } @@ -839,7 +917,7 @@ function Dashboard({data,totals,onToggle,includeTransfers,onTransfersChange}){

Salden

{allSelected?'Alle Konten im monatlichen Liquiditätssaldo. Transfers sind enthalten und heben sich über alle Konten gegenseitig auf.':'Nur die ausgewählten Konten. Transfers verändern den Saldo des jeweiligen Kontos.'}

{allSelected?'Gesamtsaldo':'Saldo Auswahl'}=0?'good-text':'bad-text'}>{selectedBalance>=0?'+':''}{formatMoney(selectedBalance)}
- {visibleAccounts.length===0?:
{visibleAccounts.map(s=>

{s.account.name}

monatlicher Fix-Saldo
=0?'good-text':'bad-text'}`}>{s.balance>=0?'+':''}{formatMoney(s.balance)}
Eingänge+{formatMoney(s.income)}
Ausgänge−{formatMoney(s.expense)}
{includeTransfers&&<>
Transfers rein+{formatMoney(s.transferIn)}
Transfers raus−{formatMoney(s.transferOut)}
}
)}
} + {visibleAccounts.length===0?:
{visibleAccounts.map(s=>

{s.account.name}

monatlicher Fix-Saldo
=0?'good-text':'bad-text'}`}>{s.balance>=0?'+':''}{formatMoney(s.balance)}
Eingänge+{formatMoney(s.income)}
Ausgänge−{formatMoney(s.expense)}
{includeTransfers&&<>
+{formatMoney(s.transferIn)}
−{formatMoney(s.transferOut)}
}
)}
}

Hinweise

FixFin prüft deinen aktuellen Plan auf Liquiditätslücken, gebündelte Fälligkeiten, anstehende Änderungen und auslaufende Positionen.

w.kind==='danger')?'danger':''}`}>{warnings.length}
{warnings.length===0?
Keine AuffälligkeitenFür die nächsten Monate sieht der Plan unauffällig aus.
:
{warnings.map((w,i)=>
{w.title}{w.text}
)}
}
@@ -856,7 +934,7 @@ function PlanningPage({data,totals}){ return
Liquiditätsplanung: Transfers zählen hier immer als echte Kontobewegungen. Eine Überweisung zu Depot, Bausparer oder Gemeinschaftskonto reduziert deshalb den Monatssaldo des Quellkontos, ohne zur Kostenstatistik zu werden.
0} positive={totalFundingNeed===0}/>
-

Benötigt pro Konto

Die geschlossene Zeile zeigt nur Konto und Ergebnis. Aufklappen zeigt, wie sich der Liquiditätsbedarf zusammensetzt.

{totals.accounts.length===0?:
{totals.accounts.map(s=>
{s.account.name}
{accountOwnerships[s.account.ownership]||accountOwnerships.unassigned}{accountFunctionLabel(s.account)}
0?'bad-text':'good-text'}>{s.fundingNeed>0?`${formatMoney(s.fundingNeed)} fehlen`:`${formatMoney(s.surplus)} übrig`}
Echte Ausgaben{formatMoney(s.expense)}
Transfers raus{formatMoney(s.transferOut)}
Echte Eingänge{formatMoney(s.income)}
Transfers rein{formatMoney(s.transferIn)}
Zusätzliche Zuführung0?'bad-text':'good-text'}>{formatMoney(s.fundingNeed)}

Ausgaben + Transfers raus − Eingänge − Transfers rein. Nur ein positiver Rest ist zusätzliche Zuführung.

)}
}
+

Benötigt pro Konto

Die geschlossene Zeile zeigt nur Konto und Ergebnis. Aufklappen zeigt, wie sich der Liquiditätsbedarf zusammensetzt.

{totals.accounts.length===0?:
{totals.accounts.map(s=>
{s.account.name}
{accountOwnerships[s.account.ownership]||accountOwnerships.unassigned}{accountFunctionLabel(s.account)}
0?'bad-text':'good-text'}>{s.fundingNeed>0?`${formatMoney(s.fundingNeed)} fehlen`:`${formatMoney(s.surplus)} übrig`}
{formatMoney(s.expense)}
{formatMoney(s.transferOut)}
{formatMoney(s.income)}
{formatMoney(s.transferIn)}
Zusätzliche Zuführung0?'bad-text':'good-text'}>{formatMoney(s.fundingNeed)}

Ausgaben + Transfers raus − Eingänge − Transfers rein. Nur ein positiver Rest ist zusätzliche Zuführung.

)}
}

Rücklagenübersicht

Periodische echte Ausgaben auf einen gleichmäßigen Monatsbetrag heruntergerechnet.

{formatMoney(reserves.annual)} / Jahr{formatMoney(reserves.monthly)} / Monat
{reserves.items.length===0?:
{reserves.items.map(x=>
{x.name}{accountName(data,x.accountId)} · {categoryName(data,x.categoryId)} · {intervals[x.interval].label}
{formatMoney(x.annualNeed)} / Jahr{formatMoney(x.monthlyReserve)} / Monat
)}
}
; @@ -925,8 +1003,8 @@ function Transfers({data,onEdit,onDuplicate,onDelete}){ const personLabelLocal=item=>transferPeopleLabel(data,item); const dueText=movementDueText; return

Fixe Transfers

Quelle, Ziel, Betrag und Einordnung bleiben direkt sichtbar. Weitere Details und Aktionen liegen im Akkordeon.

{data.transfers.length===0?:
{data.transfers.map(x=>{const cls=transferClassification(data,x);return
-
{x.name}{accountNameLocal(x.fromAccountId)} {accountNameLocal(x.toAccountId)}
{cls.label}{personLabelLocal(x)}{categoryNameLocal(x.categoryId)}{intervals[x.interval||'monthly'].label}{x.interval!=='monthly'&&{dueText(x)}}{x.endDate&&bis {formatDate(x.endDate)}}
{formatMoney(effectiveAmount(x))}{movementAmountHint(x)}
-
Von{accountNameLocal(x.fromAccountId)}
Nach{accountNameLocal(x.toAccountId)}
Personen{personLabelLocal(x)}
Einordnung{cls.description}
Kategorie{categoryNameLocal(x.categoryId)}
Intervall{intervals[x.interval||'monthly'].label}
Fälligkeit{dueText(x)}
{x.interval==='once'?'Einmalig':'Ø / Monat'}{x.interval==='once'?formatMoney(effectiveAmount(x)):formatMoney(monthlyEquivalent(x))}
{x.endDate&&
Enddatum{formatDate(x.endDate)}
}
+
{x.name}{accountNameLocal(x.fromAccountId)} {accountNameLocal(x.toAccountId)}
{cls.label}{personLabelLocal(x)}{categoryNameLocal(x.categoryId)}{intervals[x.interval||'monthly'].label}{x.interval!=='monthly'&&{dueText(x)}}{(x.amountChanges||[]).length>0&&{x.amountChanges.length} geplant}{x.endDate&&bis {formatDate(x.endDate)}}
{formatMoney(effectiveAmount(x))}{movementAmountHint(x)}
+
Von{accountNameLocal(x.fromAccountId)}
Nach{accountNameLocal(x.toAccountId)}
Personen{personLabelLocal(x)}
Einordnung{cls.description}
Kategorie{categoryNameLocal(x.categoryId)}
Intervall{intervals[x.interval||'monthly'].label}
Fälligkeit{dueText(x)}
{x.interval==='once'?'Einmalig':'Ø / Monat'}{x.interval==='once'?formatMoney(effectiveAmount(x)):formatMoney(monthlyEquivalent(x))}
{x.endDate&&
Enddatum{formatDate(x.endDate)}
}
{(x.amountChanges||[]).length>0&&
Geplante Betragsänderungen{x.amountChanges.map(c=>
ab {formatDate(c.effectiveFrom)}{formatMoney(c.amount)}
)}
}
})}
}
; } @@ -945,7 +1023,7 @@ function transferTypeStats(data,accountId='all',personId='all',year=new Date().g function TransferTypeOverview({data,accountId='all',personId='all',year=new Date().getFullYear()}){ const stats=useMemo(()=>transferTypeStats(data,accountId,personId,year),[data,accountId,personId,year]); const help=<>

Transfers werden nach Eigentum und Kontofunktion automatisch klassifiziert, z. B. Gemeinschaftsfinanzierung, Rücklagenzuführung oder Transfer zwischen Personen.

Die Beträge sind Ø-Monatswerte des gewählten Jahres. Sie werden nie zu echten Einnahmen oder Kosten addiert. Bei einem einzelnen Konto verändern sie aber den Liquiditäts-Nettofluss.

; - return

Separater Blick auf Umbuchungen {year}. Jede Karte lässt sich bis zu den konkreten Transfers aufklappen.

{stats.rows.length===0?:
{stats.rows.map(row=>
{row.label}{row.description} · {row.count} {row.count===1?'Position':'Positionen'}
{formatMoney(row.amount)} Ø / Monat{formatMoney(row.annual)} / Jahr{!stats.isAll&&
rein {formatMoney(row.in)}raus {formatMoney(row.out)}
}
)}
}
; + return

Separater Blick auf Umbuchungen {year}. Jede Karte lässt sich bis zu den konkreten Transfers aufklappen.

{stats.rows.length===0?:
{stats.rows.map(row=>
{row.label}{row.description} · {row.count} {row.count===1?'Position':'Positionen'}
{formatMoney(row.amount)} Ø / Monat{formatMoney(row.annual)} / Jahr{!stats.isAll&&
}
)}
}
; } function CategoryDistribution({title,subtitle,stats,tone,data,year}){ @@ -956,8 +1034,8 @@ function CategoryDistribution({title,subtitle,stats,tone,data,year}){ return `${item._statType==='income'?'Eingang':'Ausgang'} · ${accountName(data,item.accountId)}`; } const help=<>

Der Kategorienwert ist die Summe der Ø-Monatswerte {year} aller enthaltenen Positionen. Jede Position wird zuerst für das ganze Jahr geplant und anschließend durch 12 geteilt.

Bei einem Personenfilter werden gemeinsam zugeordnete Ausgaben gleichmäßig auf die ausgewählten Personen aufgeteilt. In der Gesamt-Personensicht werden persönliche Einnahmen nach Kontoeigentum bestimmt.

Der Prozentwert einer Kategorie ist Kategorienwert ÷ Gesamtsumme dieses Blocks.

; - return

{subtitle}

{formatMoney(stats.total)} Ø / Monat{formatMoney(stats.annual)} / Jahr
- {stats.rows.length===0?:
{stats.rows.map((row,index)=>
{index+1}{row.name}{row.count} {row.count===1?'Position':'Positionen'}
{formatMoney(row.amount)}{row.percent.toFixed(1).replace('.',',')} %
{formatMoney(row.annual)} im Jahr ÷ 12= {formatMoney(row.amount)} Ø / Monat
{row.items.sort((a,b)=>b.statAmount-a.statAmount).map((item,i)=>
{item.name}{itemMeta(item)} · {personListLabel(data,item)} · {statAmountMeta(item)}
{formatMoney(item.statAmount)} Ø / Monat{formatMoney(item.statAnnual)} / Jahr
)}
)}
} + return

{subtitle}

{formatMoney(stats.total)} Ø / Monat{formatMoney(stats.annual)} / Jahr
+ {stats.rows.length===0?:
{stats.rows.map((row,index)=>
{index+1}{row.name}{row.count} {row.count===1?'Position':'Positionen'}
{formatMoney(row.amount)}{row.percent.toFixed(1).replace('.',',')} %
{formatMoney(row.annual)} im Jahr ÷ 12= {formatMoney(row.amount)} Ø / Monat
{row.items.sort((a,b)=>b.statAmount-a.statAmount).map((item,i)=>
{item.name}{itemMeta(item)} · {personListLabel(data,item)} · {statAmountMeta(item)}
{formatMoney(item.statAmount)} Ø / Monat{formatMoney(item.statAnnual)} / Jahr
)}
)}
}
; } @@ -1005,7 +1083,7 @@ function StatisticsPage({data,includeTransfers,onTransfersChange}){

{accountId==='all'&&personId!=='all'?'Nur echte Eingänge auf persönlichen Konten, deren Eigentümer die ausgewählte Person ist. Gemeinsame Einnahmen werden nicht als persönliches Einkommen gezählt.':accountId!=='all'&&personId!=='all'?'Echte Eingänge auf dem ausgewählten Konto, anteilig gemäß Personenzuordnung der Bewegung.':'Echte Eingänge der aktuellen Auswahl.'}

Jeder Wert ist Jahresbetrag ÷ 12.

}>

Nur echte Ausgaben. Bei einer Person wird eine Bewegung mit mehreren zugeordneten Personen gleichmäßig geteilt.

Transfers bleiben vollständig getrennt.

}>
=0?'good':'bad'} help={<>

{accountId==='all'?(personId==='all'?'Wirtschaftlicher Saldo: echte Einnahmen minus echte Kosten.':'Wirtschaftlich verfügbar: persönliches Einkommen minus persönliche Netto-Kostenlast nach gemeinsamer Deckung.'):'Liquiditäts-Nettofluss des Kontos: echte Eingänge + Transfers rein − echte Ausgaben − Transfers raus.'}

}>
-

Anzahl echter Eingangs- und Ausgangspositionen der aktuellen Auswahl ohne Kategorie.

Transfers werden hier bewusst nicht mitgezählt, damit der Wert nicht vom Schalter „Transferdetails anzeigen“ abhängt.

}>
+ }>
{accountId==='all'&&} @@ -1063,7 +1141,7 @@ function YearPage({data,includeTransfers,onTransfersChange}){ return
{r.name}{visibleItems.length===0?'Keine sichtbaren Fälligkeiten':`${visibleItems.length} ${visibleItems.length===1?'Position':'Positionen'}`}
{labels.income}+{formatMoney(r.displayIncome)}
{labels.expense}−{formatMoney(r.displayExpense)}
{labels.balance}=0?'good-text':'bad-text'}>{r.balance>=0?'+':''}{formatMoney(r.balance)}
{visibleItems.length===0?
In diesem Monat sind keine sichtbaren Fixpositionen fällig. Transferdetails können über den Schalter eingeblendet werden.
:forecast.isAll?<>{visibleItems.map(detailRow)}{includeTransfers&&(r.transferIn>0||r.transferOut>0)&&
Transfervolumen in diesem Monat: {formatMoney(r.transferOut)}. Es bleibt vollständig außerhalb von Einnahmen, Ausgaben und Überschuss.
}:<> -
Echte Eingänge+{formatMoney(r.income)}
{includeTransfers&&
Transfers rein+{formatMoney(r.transferIn)}
}
Echte Ausgaben−{formatMoney(r.expense)}
{includeTransfers&&
Transfers raus−{formatMoney(r.transferOut)}
}
+
+{formatMoney(r.income)}
{includeTransfers&&
+{formatMoney(r.transferIn)}
}
−{formatMoney(r.expense)}
{includeTransfers&&
−{formatMoney(r.transferOut)}
}
{inflows.length>0&&
Zuflüsse+{formatMoney(r.displayIncome)}
{inflows.map(detailRow)}
} {outflows.length>0&&
Abflüsse−{formatMoney(r.displayExpense)}
{outflows.map(detailRow)}
}
Kontosicht: Der Nettofluss bleibt immer liquiditätsbasiert. Transfers sind keine Kosten, können hier aber separat ein- oder ausgeblendet werden.
@@ -1174,7 +1252,7 @@ function TransferAnalysisPage({data}){

Diese Quote beantwortet: Wie viel des echten Einkommens der ausgewählten Quellkonten wird über die aktuell ausgewählten Transfers weiterverteilt?

Formel: ausgewähltes Transfervolumen ÷ echte Einnahmen der ausgewählten Quellkonten × 100.

Transfers, die auf einem Quellkonto eingehen, zählen nicht als Einnahmen. Die Quote kann über 100 % liegen, wenn zusätzlich vorhandenes Guthaben oder eingehende Transfers weiterverteilt werden.

}>
{sourceIds.length===0||targetIds.length===0?
:stats.rows.length===0?
:<> -

Wie viel kommt von welchem Konto?

Jede Quelle zeigt ihr gesamtes Transfervolumen, die Verteilung auf die ausgewählten Zielkonten und den Anteil dieses Volumens an den echten Einnahmen des Quellkontos.

Transferquote = ausgewählte Transfers dieses Quellkontos ÷ echte Einnahmen dieses Quellkontos × 100.

Nur echte Eingänge zählen als Einnahmen. Eingehende Transfers sind kein Einkommen. Deshalb kann die Quote auch über 100 % liegen.

{stats.sources.map(source=>
{accountName(data,source.accountId)}{source.items.length} {source.items.length===1?'Transfer':'Transfers'} · {percent(source.annual,stats.annual)} des ausgewählten Volumens · {percentValue(source.incomeRatio)} der Einnahmen
{formatMoney(source.annual)} / Jahr{formatMoney(source.monthly)} Ø / Monat
Echte Einnahmen{formatMoney(source.incomeAnnual)} / Jahr
Ausgewählte Transfers{formatMoney(source.annual)} / Jahr
Transferquote{percentValue(source.incomeRatio)}
{source.targets.map(target=>
{accountName(data,target.accountId)}{target.items.length} {target.items.length===1?'Position':'Positionen'}
{formatMoney(target.annual)}{percent(target.annual,source.annual)}
)}
{source.items.sort((a,b)=>b.annual-a.annual).map(item=>
{item.name}{accountName(data,item.fromAccountId)} → {accountName(data,item.toAccountId)}
{formatMoney(item.annual)} / Jahr{percentValue(item.incomeRatio)} der Einnahmen der Quelle
{item.classification.label}
Betrag je Intervall{formatMoney(item.amount)}
Intervall{intervalLabel(item)}
Fälligkeit{movementDueText(item)}
Personen{transferPeopleLabel(data,item)}
Kategorie{categoryName(data,item.categoryId)}
Anteil dieser Quelle{percent(item.annual,source.annual)}
Echte Einnahmen Quelle{formatMoney(item.sourceIncomeAnnual)} / Jahr
Transfer / Einnahmen Quelle{percentValue(item.incomeRatio)}
)}
)}
+

Wie viel kommt von welchem Konto?

Jede Quelle zeigt ihr gesamtes Transfervolumen, die Verteilung auf die ausgewählten Zielkonten und den Anteil dieses Volumens an den echten Einnahmen des Quellkontos.

Transferquote = ausgewählte Transfers dieses Quellkontos ÷ echte Einnahmen dieses Quellkontos × 100.

Nur echte Eingänge zählen als Einnahmen. Eingehende Transfers sind kein Einkommen. Deshalb kann die Quote auch über 100 % liegen.

{stats.sources.map(source=>
{accountName(data,source.accountId)}{source.items.length} {source.items.length===1?'Transfer':'Transfers'} · {percent(source.annual,stats.annual)} des ausgewählten Volumens · {percentValue(source.incomeRatio)} der Einnahmen
{formatMoney(source.annual)} / Jahr{formatMoney(source.monthly)} Ø / Monat
Echte Einnahmen{formatMoney(source.incomeAnnual)} / Jahr
Ausgewählte Transfers{formatMoney(source.annual)} / Jahr
Transferquote{percentValue(source.incomeRatio)}
{source.targets.map(target=>
{accountName(data,target.accountId)}{target.items.length} {target.items.length===1?'Position':'Positionen'}
{formatMoney(target.annual)}{percent(target.annual,source.annual)}
)}
{source.items.sort((a,b)=>b.annual-a.annual).map(item=>
{item.name}{accountName(data,item.fromAccountId)} → {accountName(data,item.toAccountId)}
{formatMoney(item.annual)} / Jahr{percentValue(item.incomeRatio)} der Einnahmen der Quelle
{item.classification.label}
{(item.amountChanges||[]).length?'Basisbetrag je Intervall':'Betrag je Intervall'}{formatMoney(item.amount)}
Intervall{intervalLabel(item)}
Fälligkeit{movementDueText(item)}
Personen{transferPeopleLabel(data,item)}
Kategorie{categoryName(data,item.categoryId)}
Anteil dieser Quelle{percent(item.annual,source.annual)}
Echte Einnahmen Quelle{formatMoney(item.sourceIncomeAnnual)} / Jahr
Transfer / Einnahmen Quelle{percentValue(item.incomeRatio)}
{(item.amountChanges||[]).length>0&&
Geplante Betragsänderungen{item.amountChanges.map(change=>`ab ${formatDate(change.effectiveFrom)}: ${formatMoney(change.amount)}`).join(' · ')}
}
)}
)}

Verteilung auf die Zielkonten

Wie viel des ausgewählten Transfervolumens insgesamt auf jedem Zielkonto landet. Zusätzlich siehst du, wie groß dieser Transferzufluss im Verhältnis zu den echten Einnahmen des Zielkontos ist.

Diese Zielquote ist eine Ergänzung zur Quellquote: eingehende ausgewählte Transfers ÷ echte Einnahmen des Zielkontos × 100.

Hat das Zielkonto keine echten Einnahmen, wird keine Prozentquote berechnet. Das ist kein Fehler, sondern bedeutet, dass das Konto ausschließlich oder zusätzlich durch Transfers gespeist wird.

{stats.targets.map(target=>
{accountName(data,target.accountId)}{target.items.length} {target.items.length===1?'Transfer':'Transfers'}
{formatMoney(target.annual)} / Jahr
{formatMoney(target.monthly)} Ø / Monat{percent(target.annual,stats.annual)}
Echte Einnahmen: {formatMoney(target.incomeAnnual)}Transferzufluss / Einnahmen: {percentValue(target.incomeRatio)}
)}
@@ -1282,7 +1360,7 @@ function EditorDialog({dialog,data,onClose,onSave}){ if(!selectedPersonIds.includes(newId))selectedPersonIds.push(newId); } if(selectedPersonIds.length===0)return setLocalError('Bitte mindestens eine Person auswählen.'); - if(type==='transfers') value={...common,fromAccountId,toAccountId,categoryId:selectedCategoryId,personIds:selectedPersonIds}; + if(type==='transfers') value={...common,fromAccountId,toAccountId,categoryId:selectedCategoryId,personIds:selectedPersonIds,amountChanges}; else if(type==='expenses') value={...common,accountId,categoryId:selectedCategoryId,personIds:selectedPersonIds,amountChanges,pushoverReminder}; else value={...common,accountId,categoryId:selectedCategoryId,personIds:selectedPersonIds,amountChanges}; } @@ -1299,7 +1377,7 @@ function EditorDialog({dialog,data,onClose,onSave}){ {!showNewPerson?:
} } {(type==='incomes'||type==='expenses')&&} - {(type==='incomes'||type==='expenses')&&interval!=='once'&&
Geplante BetragsänderungenEin neuer Betrag gilt ab dem Monat des gewählten Datums. Der Basisbetrag oben bleibt für frühere Monate erhalten.
{amountChanges.length}
{amountChanges.length>0&&
{amountChanges.map(c=>
ab {formatDate(c.effectiveFrom)}{formatMoney(c.amount)}
)}
}
} + {(type==='incomes'||type==='expenses'||type==='transfers')&&interval!=='once'&&
Geplante BetragsänderungenEin neuer Betrag gilt ab dem Monat des gewählten Datums. Der Basisbetrag oben bleibt für frühere Monate erhalten.
{amountChanges.length}
{amountChanges.length>0&&
{amountChanges.map(c=>
ab {formatDate(c.effectiveFrom)}{formatMoney(c.amount)}
)}
}
} {type==='expenses'&&} {type==='transfers'&&<>}
; } diff --git a/src/styles.css b/src/styles.css index 7bbf036..5658eec 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1531,3 +1531,38 @@ html.modal-open, body.modal-open { overflow:hidden !important; overscroll-behavi .transfer-analysis-summary { grid-template-columns:repeat(2,minmax(0,1fr)); } .transfer-income-ratio-strip { grid-template-columns:1fr; } } + + +/* v3.34: vollständige Statistik-Erklärungen */ +.stat-term-label { display:inline-flex; align-items:center; gap:5px; min-width:0; vertical-align:middle; } +.stat-term-label > span { min-width:0; } +.stat-term-label .stats-info-button { width:17px; height:17px; flex:0 0 17px; padding:0; font-size:9px; border-color:#526782; background:#132033; color:#dce7f5; } +.stat-term-label .stats-info-button:hover { background:#263a59; border-color:#88a1c4; } +.stats-term-explanation { display:grid; gap:12px; } +.stats-term-explanation p { margin:0; padding:10px 11px; border:1px solid #2b3b52; border-radius:10px; background:#0d151f; color:#c1ccda; } +.stats-term-explanation p strong { color:#f2f5fa; } +.stats-term-explanation .stats-example { background:#111d2c; border-color:#3a5272; } +.stats-glossary-button { white-space:nowrap; align-self:flex-start; } +.stats-glossary { display:grid; gap:18px; max-height:62vh; overflow:auto; padding-right:4px; } +.stats-glossary > section { display:grid; gap:8px; } +.stats-glossary h3 { margin:0; color:#f0f4fa; font-size:13px; } +.stats-glossary-list { display:grid; gap:7px; } +.stats-glossary-list details { border:1px solid #2a394f; border-radius:10px; background:#0b121b; overflow:hidden; } +.stats-glossary-list summary { display:flex; justify-content:space-between; align-items:center; gap:10px; padding:10px 12px; cursor:pointer; color:#e6edf7; font-weight:700; font-size:10px; } +.stats-glossary-list details[open] summary { border-bottom:1px solid #26364b; } +.stats-glossary-list .stats-term-explanation { padding:10px; } +.category-percent-with-help { display:inline-flex !important; align-items:center; justify-content:flex-end; gap:4px; } +.category-percent-with-help .stats-info-button { width:16px; height:16px; min-width:16px; font-size:8px; } +.shared-finance-summary .stat-term-label, +.person-flow-grid .stat-term-label, +.shared-person-row .stat-term-label, +.shared-income-burden-summary .stat-term-label, +.shared-income-person-row .stat-term-label { color:inherit; } +.stat-formula-rows .stat-term-label .stats-info-button { width:16px; height:16px; min-width:16px; font-size:8px; } +@media (max-width:700px){ + .stats-glossary-button { width:100%; justify-content:center; } + .stats-glossary { max-height:68vh; } +} + +.stats-context-help { display:grid; gap:7px; padding-top:10px; border-top:1px solid #314157; } +.stats-context-help > strong { color:#eef2f8; }