From 4012170172927cf4a404e25dbc68b38d9a199442 Mon Sep 17 00:00:00 2001 From: mboehmlaender Date: Wed, 19 Aug 2026 12:39:14 +0200 Subject: [PATCH] V3.18 --- install.sh | 0 public/sw.js | 2 +- server/index.js | 2 +- server/reports.js | 11 ++++++++++- start-local.sh | 0 5 files changed, 12 insertions(+), 3 deletions(-) mode change 100755 => 100644 install.sh mode change 100755 => 100644 start-local.sh diff --git a/install.sh b/install.sh old mode 100755 new mode 100644 diff --git a/public/sw.js b/public/sw.js index 92a6511..f4feb9f 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,4 +1,4 @@ -const SHELL_CACHE='fixfin-shell-v3.17'; +const SHELL_CACHE='fixfin-shell-v3.18'; 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 30c0f86..d42af4e 100644 --- a/server/index.js +++ b/server/index.js @@ -77,7 +77,7 @@ async function readPushoverConfig(){const value=await readJsonFile(pushoverFile, async function savePushoverConfig(input){const token=String(input?.token||'').trim();const user=String(input?.user||'').trim();if(!pushoverKeyPattern.test(token))throw new Error('Der Pushover Application API Token muss 30 alphanumerische Zeichen enthalten.');if(!pushoverKeyPattern.test(user))throw new Error('Der Pushover User/Group Key muss 30 alphanumerische Zeichen enthalten.');await writePrivateJson(pushoverFile,{token,user,updatedAt:new Date().toISOString()});return {configured:true,userMasked:maskKey(user)};} async function sendPushover(config,{title,message}){ const body=new URLSearchParams({token:config.token,user:config.user,title,message}); - const response=await fetch(PUSHOVER_ENDPOINT,{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded','User-Agent':'FixFin/3.17'},body}); + const response=await fetch(PUSHOVER_ENDPOINT,{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded','User-Agent':'FixFin/3.18'},body}); let payload={}; try{payload=await response.json();}catch{} if(!response.ok||payload.status!==1)throw new Error(Array.isArray(payload.errors)?payload.errors.join(' '):`Pushover-API Fehler (${response.status})`); return payload; diff --git a/server/reports.js b/server/reports.js index c4dd964..25b8583 100644 --- a/server/reports.js +++ b/server/reports.js @@ -103,7 +103,16 @@ export async function createPdfReport(data,{year,accountId}={}){ const cols=[{label:'Art',width:88},{label:'Bezeichnung',width:167},{label:'Kategorie',width:105},{label:'Zufluss',width:68,align:'right'},{label:'Abfluss',width:67,align:'right'}]; for(let monthIndex=0;monthIndex<12;monthIndex++){ const monthRows=rows.filter(r=>r.monthIndex===monthIndex);const month=forecast.rows[monthIndex]; - addPageIfNeeded(doc,115);doc.fillColor('#17233B').font('Helvetica-Bold').fontSize(14).text(month.name);doc.moveDown(.35); + + // Jeder Monat beginnt auf einer eigenen Seite. Der Monatsname steht immer + // links und nutzt die volle Inhaltsbreite, damit er niemals umbrechen kann. + doc.addPage(); + drawPdfHeader(doc,`Kontoauszug ${reportYear}`,account.name); + const monthY=doc.y; + doc.fillColor('#17233B').font('Helvetica-Bold').fontSize(16) + .text(month.name,50,monthY,{width:495,align:'left',lineBreak:false}); + doc.y=monthY+24; + pdfMetricRow(doc,[{label:'Zuflüsse',value:pdfMoney(month.displayIncome),positive:true},{label:'Abflüsse',value:pdfMoney(month.displayExpense),negative:true},{label:'Nettofluss',value:pdfMoney(month.balance),positive:month.balance>=0,negative:month.balance<0}]); if(!monthRows.length){doc.fillColor('#667085').font('Helvetica').fontSize(8).text('Keine geplanten Kontobewegungen.');doc.moveDown(.8);continue;} pdfTableHeader(doc,cols); diff --git a/start-local.sh b/start-local.sh old mode 100755 new mode 100644