const express = require('express'); const { Pool } = require('pg'); const { v4: uuidv4 } = require('uuid'); const router = express.Router(); // Database pool const pool = new Pool({ host: process.env.DB_HOST || 'db', port: process.env.DB_PORT || 5432, database: process.env.DB_NAME || 'steuer', user: process.env.DB_USER || 'app', password: process.env.DB_PASSWORD || 'app123', }); // GET /api/invoices - Alle Rechnungen router.get('/', async (req, res) => { try { const { status, customer, year, limit = 100, offset = 0 } = req.query; let query = ` SELECT r.*, COALESCE(SUM(b.betrag), 0) as bezahlt FROM rechnungen r LEFT JOIN rechnung_zahlungen b ON r.id = b.rechnung_id WHERE 1=1 `; const values = []; let paramCount = 0; if (status) { paramCount++; query += ` AND r.status = $${paramCount}`; values.push(status); } if (customer) { paramCount++; query += ` AND r.kunde ILIKE $${paramCount}`; values.push(`%${customer}%`); } if (year) { paramCount++; query += ` AND EXTRACT(YEAR FROM r.datum) = $${paramCount}`; values.push(year); } query += ` GROUP BY r.id ORDER BY r.datum DESC LIMIT $${++paramCount} OFFSET $${++paramCount}`; values.push(limit, offset); const result = await pool.query(query, values); // Summen berechnen const summaryQuery = ` SELECT COUNT(*) as total_count, COALESCE(SUM(betrag), 0) as total_betrag, COALESCE(SUM(CASE WHEN status = 'offen' THEN betrag ELSE 0 END), 0) as open_total, COALESCE(SUM(CASE WHEN status = 'bezahlt' THEN betrag ELSE 0 END), 0) as paid_total FROM rechnungen WHERE 1=1 ${status ? ' AND status = $1' : ''} ${year ? (status ? ' AND' : '') + ` EXTRACT(YEAR FROM datum) = $${status ? 2 : 1}` : ''} `; const summaryValues = []; if (status) summaryValues.push(status); if (year) summaryValues.push(year); const summaryResult = await pool.query(summaryQuery, summaryValues); res.json({ success: true, invoices: result.rows.map(row => ({ ...row, betrag: parseFloat(row.betrag) || 0, ust_satz: parseFloat(row.ust_satz) || 19, bezahlt: parseFloat(row.bezahlt) || 0, offen: (parseFloat(row.betrag) || 0) - (parseFloat(row.bezahlt) || 0) })), summary: { total_count: parseInt(summaryResult.rows[0].total_count) || 0, total_betrag: parseFloat(summaryResult.rows[0].total_betrag) || 0, open_total: parseFloat(summaryResult.rows[0].open_total) || 0, paid_total: parseFloat(summaryResult.rows[0].paid_total) || 0 } }); } catch (error) { console.error('Invoices List Error:', error); res.status(500).json({ success: false, error: error.message }); } }); // GET /api/invoices/:id - Einzelne Rechnung router.get('/:id', async (req, res) => { try { const { id } = req.params; const [rechnungResult, zahlungenResult] = await Promise.all([ pool.query('SELECT * FROM rechnungen WHERE id = $1', [id]), pool.query(` SELECT * FROM rechnung_zahlungen WHERE rechnung_id = $1 ORDER BY datum DESC `, [id]) ]); if (rechnungResult.rows.length === 0) { return res.status(404).json({ success: false, error: 'Rechnung nicht gefunden' }); } const rechnung = rechnungResult.rows[0]; const zahlungen = zahlungenResult.rows; const bezahlt = zahlungen.reduce((sum, z) => sum + parseFloat(z.betrag || 0), 0); res.json({ success: true, invoice: { ...rechnung, betrag: parseFloat(rechnung.betrag) || 0, ust_satz: parseFloat(rechnung.ust_satz) || 19, zahlungen, bezahlt, offen: (parseFloat(rechnung.betrag) || 0) - bezahlt } }); } catch (error) { console.error('Invoice Detail Error:', error); res.status(500).json({ success: false, error: error.message }); } }); // POST /api/invoices - Rechnung erstellen router.post('/', async (req, res) => { try { const { kunde, kunde_email, kunde_adresse, leistung, betrag, ust_satz = 19, datum, faelligkeit, rechnung_nr } = req.body; if (!kunde || !betrag) { return res.status(400).json({ success: false, error: 'Kunde und Betrag sind erforderlich' }); } // Rechnungsnummer generieren falls nicht angegeben let finalRechnungNr = rechnung_nr; if (!finalRechnungNr) { const countResult = await pool.query(` SELECT COUNT(*) FROM rechnungen WHERE EXTRACT(YEAR FROM datum) = EXTRACT(YEAR FROM CURRENT_DATE) `); const count = parseInt(countResult.rows[0].count) + 1; finalRechnungNr = `RE-${new Date().getFullYear()}-${String(count).padStart(3, '0')}`; } const query = ` INSERT INTO rechnungen ( id, rechnung_nr, kunde, kunde_email, kunde_adresse, leistung, betrag, ust_satz, datum, faelligkeit, status, created_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'offen', NOW()) RETURNING * `; const values = [ uuidv4(), finalRechnungNr, kunde, kunde_email || null, kunde_adresse || null, leistung || null, betrag, ust_satz, datum || new Date(), faelligkeit || null ]; const result = await pool.query(query, values); res.status(201).json({ success: true, invoice: result.rows[0] }); } catch (error) { console.error('Invoice Create Error:', error); res.status(500).json({ success: false, error: error.message }); } }); // PUT /api/invoices/:id - Rechnung aktualisieren router.put('/:id', async (req, res) => { try { const { id } = req.params; const { kunde, kunde_email, kunde_adresse, leistung, betrag, ust_satz, datum, faelligkeit, status } = req.body; const query = ` UPDATE rechnungen SET kunde = COALESCE($1, kunde), kunde_email = COALESCE($2, kunde_email), kunde_adresse = COALESCE($3, kunde_adresse), leistung = COALESCE($4, leistung), betrag = COALESCE($5, betrag), ust_satz = COALESCE($6, ust_satz), datum = COALESCE($7, datum), faelligkeit = COALESCE($8, faelligkeit), status = COALESCE($9, status) WHERE id = $10 RETURNING * `; const values = [ kunde, kunde_email, kunde_adresse, leistung, betrag, ust_satz, datum, faelligkeit, status, id ]; const result = await pool.query(query, values); if (result.rows.length === 0) { return res.status(404).json({ success: false, error: 'Rechnung nicht gefunden' }); } res.json({ success: true, invoice: result.rows[0] }); } catch (error) { console.error('Invoice Update Error:', error); res.status(500).json({ success: false, error: error.message }); } }); // DELETE /api/invoices/:id - Rechnung löschen router.delete('/:id', async (req, res) => { try { const { id } = req.params; // Lösche zuerst Zahlungen await pool.query('DELETE FROM rechnung_zahlungen WHERE rechnung_id = $1', [id]); const result = await pool.query('DELETE FROM rechnungen WHERE id = $1 RETURNING *', [id]); if (result.rows.length === 0) { return res.status(404).json({ success: false, error: 'Rechnung nicht gefunden' }); } res.json({ success: true, message: 'Rechnung erfolgreich gelöscht', deleted: result.rows[0] }); } catch (error) { console.error('Invoice Delete Error:', error); res.status(500).json({ success: false, error: error.message }); } }); // POST /api/invoices/:id/payment - Zahlung hinzufügen router.post('/:id/payment', async (req, res) => { try { const { id } = req.params; const { betrag, datum, methode, notizen } = req.body; if (!betrag) { return res.status(400).json({ success: false, error: 'Betrag ist erforderlich' }); } const query = ` INSERT INTO rechnung_zahlungen (id, rechnung_id, betrag, datum, methode, notizen, created_at) VALUES ($1, $2, $3, $4, $5, $6, NOW()) RETURNING * `; const values = [uuidv4(), id, betrag, datum || new Date(), methode || 'Überweisung', notizen || null]; const result = await pool.query(query, values); // Prüfe ob Rechnung nun vollständig bezahlt const [rechnungResult, zahlungenSumResult] = await Promise.all([ pool.query('SELECT betrag FROM rechnungen WHERE id = $1', [id]), pool.query('SELECT COALESCE(SUM(betrag), 0) as total FROM rechnung_zahlungen WHERE rechnung_id = $1', [id]) ]); const rechnungBetrag = parseFloat(rechnungResult.rows[0].betrag); const bezahlt = parseFloat(zahlungenSumResult.rows[0].total); if (bezahlt >= rechnungBetrag) { await pool.query(`UPDATE rechnungen SET status = 'bezahlt' WHERE id = $1`, [id]); } else if (bezahlt > 0) { await pool.query(`UPDATE rechnungen SET status = 'teilweise' WHERE id = $1`, [id]); } res.json({ success: true, payment: result.rows[0], bezahlt, offen: rechnungBetrag - bezahlt }); } catch (error) { console.error('Invoice Payment Error:', error); res.status(500).json({ success: false, error: error.message }); } }); module.exports = router;