/** * Generiert eine Excel-Datei mit QR-Codes für alle Wasserzähler. * * Verwendung: * npx tsx scripts/generate-qr-excel.ts */ import { readFileSync, existsSync, writeFileSync } from 'fs'; import { resolve } from 'path'; import { createClient } from '@supabase/supabase-js'; import QRCode from 'qrcode'; import ExcelJS from 'exceljs'; // .env.local laden function loadEnv() { const envPath = resolve(__dirname, '..', '.env.local'); if (!existsSync(envPath)) { console.error('FEHLER: .env.local nicht gefunden'); process.exit(1); } const content = readFileSync(envPath, 'utf-8'); for (const line of content.split('\n')) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const eqIndex = trimmed.indexOf('='); if (eqIndex === -1) continue; const key = trimmed.slice(0, eqIndex).trim(); const value = trimmed.slice(eqIndex + 1).trim(); if (!process.env[key]) process.env[key] = value; } } loadEnv(); const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL!; const SERVICE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY!; const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || 'https://gemeindeportal.vercel.app'; const supabase = createClient(SUPABASE_URL, SERVICE_KEY); async function main() { console.log('Lade Wasserzähler aus Supabase...'); const { data: zaehler, error } = await supabase .from('wasserzaehler') .select('*') .order('haushalt_name', { ascending: true }); if (error || !zaehler || zaehler.length === 0) { console.error('Fehler oder keine Daten:', error?.message || 'Keine Einträge'); process.exit(1); } console.log(`${zaehler.length} Einträge gefunden. Generiere Excel...\n`); const workbook = new ExcelJS.Workbook(); workbook.creator = 'Gemeindeportal'; // --- Blatt 1: Übersicht --- const listSheet = workbook.addWorksheet('Übersicht', { pageSetup: { paperSize: 9, orientation: 'landscape', fitToPage: true, fitToWidth: 1, fitToHeight: 0 }, }); listSheet.columns = [ { header: 'Kundennummer', key: 'kundennummer', width: 18 }, { header: 'Zählernummer', key: 'zaehlernummer', width: 18 }, { header: 'Name', key: 'name', width: 28 }, { header: 'Adresse', key: 'adresse', width: 30 }, { header: 'Letzter Stand (m³)', key: 'stand', width: 18 }, { header: 'QR-Code URL', key: 'url', width: 55 }, ]; const headerRow = listSheet.getRow(1); headerRow.font = { bold: true, size: 11, color: { argb: 'FFFFFFFF' } }; headerRow.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF0D2B4E' } }; headerRow.alignment = { vertical: 'middle' }; headerRow.height = 28; for (const z of zaehler) { const url = `${BASE_URL}/wasserzaehler?token=${z.access_token}`; listSheet.addRow({ kundennummer: z.kundennummer || '', zaehlernummer: z.zaehlernummer, name: z.haushalt_name, adresse: z.adresse || '', stand: z.alter_stand, url, }); } listSheet.autoFilter = { from: { row: 1, column: 1 }, to: { row: zaehler.length + 1, column: 6 }, }; // --- Je 1 Blatt pro Kunde mit QR-Code --- for (let i = 0; i < zaehler.length; i++) { const z = zaehler[i]; const url = `${BASE_URL}/wasserzaehler?token=${z.access_token}`; const qrBuffer = await QRCode.toBuffer(url, { width: 400, margin: 2, errorCorrectionLevel: 'M', type: 'png', }); const sheetName = (z.kundennummer || z.haushalt_name || `Kunde ${i + 1}`).slice(0, 31); const sheet = workbook.addWorksheet(sheetName, { pageSetup: { paperSize: 9, orientation: 'portrait', horizontalCentered: true, verticalCentered: true, margins: { left: 0.7, right: 0.7, top: 1.0, bottom: 1.0, header: 0.3, footer: 0.3 }, }, }); sheet.getColumn(1).width = 5; sheet.getColumn(2).width = 25; sheet.getColumn(3).width = 35; sheet.getColumn(4).width = 5; // Titel sheet.mergeCells('B2:C2'); const titleCell = sheet.getCell('B2'); titleCell.value = 'Gemeindeamt Weißkirchen an der Traun'; titleCell.font = { bold: true, size: 16, color: { argb: 'FF0D2B4E' } }; titleCell.alignment = { horizontal: 'center', vertical: 'middle' }; sheet.getRow(2).height = 30; sheet.mergeCells('B3:C3'); const subtitleCell = sheet.getCell('B3'); subtitleCell.value = `Wasserzähler-Ablesung ${new Date().getFullYear()}`; subtitleCell.font = { size: 12, color: { argb: 'FF666666' } }; subtitleCell.alignment = { horizontal: 'center', vertical: 'middle' }; sheet.getRow(3).height = 22; // QR Code Bild const imageId = workbook.addImage({ buffer: qrBuffer as unknown as ExcelJS.Buffer, extension: 'png' }); sheet.addImage(imageId, { tl: { col: 1.5, row: 5 }, ext: { width: 250, height: 250 } }); for (let row = 5; row <= 20; row++) sheet.getRow(row).height = 18; // Kundennummer const r = 22; sheet.mergeCells(`B${r}:C${r}`); sheet.getCell(`B${r}`).value = 'Kundennummer'; sheet.getCell(`B${r}`).font = { size: 10, color: { argb: 'FF999999' } }; sheet.getCell(`B${r}`).alignment = { horizontal: 'center' }; sheet.mergeCells(`B${r + 1}:C${r + 1}`); sheet.getCell(`B${r + 1}`).value = z.kundennummer || '—'; sheet.getCell(`B${r + 1}`).font = { bold: true, size: 22 }; sheet.getCell(`B${r + 1}`).alignment = { horizontal: 'center' }; sheet.getRow(r + 1).height = 32; // Zählernummer sheet.mergeCells(`B${r + 3}:C${r + 3}`); sheet.getCell(`B${r + 3}`).value = 'Zählernummer'; sheet.getCell(`B${r + 3}`).font = { size: 10, color: { argb: 'FF999999' } }; sheet.getCell(`B${r + 3}`).alignment = { horizontal: 'center' }; sheet.mergeCells(`B${r + 4}:C${r + 4}`); sheet.getCell(`B${r + 4}`).value = z.zaehlernummer; sheet.getCell(`B${r + 4}`).font = { bold: true, size: 16 }; sheet.getCell(`B${r + 4}`).alignment = { horizontal: 'center' }; sheet.getRow(r + 4).height = 26; // Name (klein, für Zuordnung) sheet.mergeCells(`B${r + 6}:C${r + 6}`); sheet.getCell(`B${r + 6}`).value = z.haushalt_name; sheet.getCell(`B${r + 6}`).font = { size: 11, color: { argb: 'FF999999' } }; sheet.getCell(`B${r + 6}`).alignment = { horizontal: 'center' }; // Anleitung sheet.mergeCells(`B${r + 8}:C${r + 8}`); sheet.getCell(`B${r + 8}`).value = 'Scannen Sie den QR-Code mit Ihrem Smartphone'; sheet.getCell(`B${r + 8}`).font = { size: 11, color: { argb: 'FF666666' } }; sheet.getCell(`B${r + 8}`).alignment = { horizontal: 'center', wrapText: true }; sheet.mergeCells(`B${r + 9}:C${r + 9}`); sheet.getCell(`B${r + 9}`).value = 'um Ihren aktuellen Zählerstand zu melden.'; sheet.getCell(`B${r + 9}`).font = { size: 11, color: { argb: 'FF666666' } }; sheet.getCell(`B${r + 9}`).alignment = { horizontal: 'center', wrapText: true }; console.log(` ✓ ${z.haushalt_name} (${z.kundennummer || z.zaehlernummer})`); } const outputPath = resolve(__dirname, '..', `wasserzaehler_qrcodes_${new Date().toISOString().split('T')[0]}.xlsx`); await workbook.xlsx.writeFile(outputPath); console.log(`\nExcel gespeichert: ${outputPath}`); console.log(`\nBlatt 1 "Übersicht": Tabelle mit allen Kunden + URLs`); console.log(`Blätter 2-${zaehler.length + 1}: Je 1 Druckseite pro Kunde mit QR-Code`); } main().catch((err) => { console.error('Fehler:', err); process.exit(1); });