diff --git a/index.js b/index.js index f30c9a0..144c79d 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,7 @@ const dayjs = require('dayjs'); async function runExport() { const isDebug = process.env.DEBUG_MODE === 'true'; + // Konfiguration aus .env laden const config = { email: process.env.TECHEM_EMAIL, password: process.env.TECHEM_PASSWORD, @@ -16,7 +17,7 @@ async function runExport() { selectorTimeout: parseInt(process.env.TIMEOUT_SELECTOR, 10) || 15000, }; - console.log(`🚀 Techem Export (Horizontal-Layout) gestartet...`); + console.log(`🚀 Techem Export gestartet... (Debug: ${isDebug ? 'AN' : 'AUS'})`); const browser = await chromium.launch({ headless: !isDebug, @@ -27,15 +28,20 @@ async function runExport() { const page = await context.newPage(); try { - // --- LOGIN FLOW --- - console.log('🔑 Login-Prozess...'); + // ========================================== + // 1. LOGIN & AUTHENTIFIZIERUNG + // ========================================== + console.log('🔑 Login-Prozess startet...'); await page.goto('https://mieter.techem.de/', { waitUntil: 'networkidle', timeout: config.pageTimeout }); - // Cookie-Banner weg + // Cookie-Banner killen try { const cookieBtn = await page.waitForSelector('#CybotCookiebotDialogBodyButtonDecline', { timeout: 5000 }); - if (cookieBtn) await cookieBtn.click(); - } catch (e) {} + if (cookieBtn) { + await cookieBtn.click(); + await page.waitForTimeout(1000); // Kurz warten bis Animation weg ist + } + } catch (e) { /* Kein Banner da */ } // Login-Bereich öffnen & Formular ausfüllen const loginTrigger = page.locator('text=/login|anmelden/i').first(); @@ -46,10 +52,13 @@ async function runExport() { await page.fill('input[type="password"]', config.password); await page.click('button[type="submit"]'); + console.log('⏳ Warte auf Dashboard-Weiterleitung...'); await page.waitForURL(/.*consumptions.*/, { timeout: config.pageTimeout }); console.log('✅ Login erfolgreich.'); - // --- DATEN EXTRAKTION --- + // ========================================== + // 2. DATEN EXTRAKTION + // ========================================== const months = []; let current = dayjs(config.startDate); const end = dayjs(config.endDate); @@ -67,11 +76,10 @@ async function runExport() { let wWert = 0, wUnit = 'm³'; try { - // 1. Heizung abrufen + // Heizung const urlHeating = `https://mieter.techem.de/en/${config.unitId}/consumptions/heating/${date}`; - // Turbo an: Wir warten nur auf das DOM, nicht auf Tracking-Skripte await page.goto(urlHeating, { waitUntil: 'domcontentloaded', timeout: config.pageTimeout }); - await page.waitForTimeout(1000); // Kurze 1-Sekunden-Atempause für React + await page.waitForTimeout(1000); // React kurz rendern lassen const bodyHeating = await page.innerText('body'); const matchH = bodyHeating.match(/([\d,.]+)\s*(kWh|units)/i); @@ -80,7 +88,7 @@ async function runExport() { hUnit = matchH[2]; } - // 2. Warmwasser abrufen + // Warmwasser const urlWater = `https://mieter.techem.de/en/${config.unitId}/consumptions/hot-water/${date}`; await page.goto(urlWater, { waitUntil: 'domcontentloaded', timeout: config.pageTimeout }); await page.waitForTimeout(1000); @@ -92,15 +100,7 @@ async function runExport() { wUnit = matchW[2]; } - // Daten zusammenführen - results.push({ - monat: date, - water: wWert, - waterUnit: wUnit, - heating: hWert, - heatingUnit: hUnit - }); - + results.push({ monat: date, water: wWert, waterUnit: wUnit, heating: hWert, heatingUnit: hUnit }); console.log(` ✅ HZ: ${hWert} ${hUnit} | WW: ${wWert} ${wUnit}`); } catch (err) { @@ -108,8 +108,10 @@ async function runExport() { } } - // --- EXCEL DESIGN --- - console.log('📊 Erstelle übersichtliche Excel-Datei...'); + // ========================================== + // 3. EXCEL EXPORT (Monatlich, Horizontal) + // ========================================== + console.log('📊 Erstelle Excel-Datei...'); const workbook = new ExcelJS.Workbook(); const sheet = workbook.addWorksheet('Verbrauchsübersicht'); @@ -121,7 +123,6 @@ async function runExport() { { header: 'Einheit HZ', key: 'heatingUnit', width: 12 } ]; - // Styling (Blauer Header, alternierende Zeilen) sheet.getRow(1).font = { bold: true, color: { argb: 'FFFFFFFF' } }; sheet.getRow(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF00549F' } }; sheet.getRow(1).alignment = { vertical: 'middle', horizontal: 'center' }; @@ -129,22 +130,128 @@ async function runExport() { results.forEach((data, index) => { const row = sheet.addRow(data); row.alignment = { vertical: 'middle', horizontal: 'center' }; - if (index % 2 !== 0) { - row.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF2F2F2' } }; - } + if (index % 2 !== 0) row.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF2F2F2' } }; }); sheet.autoFilter = 'A1:E1'; sheet.views = [{ state: 'frozen', ySplit: 1 }]; - const fileName = `Techem_Smart_Export_${dayjs().format('YYYY-MM-DD')}.xlsx`; - await workbook.xlsx.writeFile(fileName); + const excelName = `Techem_Export_${dayjs().format('YYYY-MM-DD')}.xlsx`; + await workbook.xlsx.writeFile(excelName); + console.log(`✨ Excel gespeichert: ${excelName}`); - console.log(`✨ Datei wurde generiert: ${fileName}`); + // ========================================== + // 4. PDF EXPORT (Jährlich mit Graphen) + // ========================================== + if (results.length > 0) { + console.log('📄 Erstelle PDF-Report...'); + + // Daten nach Jahren aggregieren + const yearlyStats = results.reduce((acc, curr) => { + const year = curr.monat.split('-')[0]; + if (!acc[year]) acc[year] = { year, heating: 0, water: 0, heatingUnit: curr.heatingUnit, waterUnit: curr.waterUnit }; + acc[year].heating += curr.heating; + acc[year].water += curr.water; + return acc; + }, {}); + const statsArray = Object.values(yearlyStats); + + const htmlContent = ` + + +
+ + + + +| Jahr | +Heizung Gesamt (${results[0].heatingUnit}) | +Warmwasser Gesamt (${results[0].waterUnit}) | +
|---|---|---|
| ${s.year} | +${s.heating.toFixed(2)} | +${s.water.toFixed(2)} | +