Backend: Neue API-Endpunkte hinzugefügt
- /api/dashboard/* - Dashboard Übersicht, Cashflow, Steuer-Preview - /api/customers/* - Kunden CRUD API - /api/invoices/* - Rechnungen CRUD API + Zahlungen Server.js angepasst um neue Routen einzubinden.
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
const express = require('express');
|
||||
const { Pool } = require('pg');
|
||||
|
||||
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/dashboard/summary
|
||||
router.get('/summary', async (req, res) => {
|
||||
try {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
// Gesamtübersichten parallel abfragen
|
||||
const [
|
||||
rechnungenResult,
|
||||
belegeResult,
|
||||
krediteResult,
|
||||
stundenResult,
|
||||
kundenResult
|
||||
] = await Promise.all([
|
||||
// Rechnungen dieses Jahr
|
||||
pool.query(`
|
||||
SELECT
|
||||
COUNT(*) as total_count,
|
||||
COALESCE(SUM(CASE WHEN status = 'bezahlt' THEN betrag ELSE 0 END), 0) as paid_total,
|
||||
COALESCE(SUM(CASE WHEN status = 'offen' THEN betrag ELSE 0 END), 0) as open_total,
|
||||
COALESCE(SUM(betrag), 0) as total
|
||||
FROM rechnungen
|
||||
WHERE EXTRACT(YEAR FROM datum) = $1
|
||||
`, [currentYear]),
|
||||
|
||||
// Belege/Ausgaben dieses Jahr
|
||||
pool.query(`
|
||||
SELECT
|
||||
COUNT(*) as total_count,
|
||||
COALESCE(SUM(betrag), 0) as total
|
||||
FROM belege
|
||||
WHERE EXTRACT(YEAR FROM date) = $1
|
||||
`, [currentYear]),
|
||||
|
||||
// Kredite
|
||||
pool.query(`
|
||||
SELECT
|
||||
COUNT(*) as total_count,
|
||||
COUNT(CASE WHEN status = 'aktiv' THEN 1 END) as active_count,
|
||||
COALESCE(SUM(CASE WHEN status = 'aktiv' THEN restschuld ELSE 0 END), 0) as total_restschuld,
|
||||
COALESCE(SUM(CASE WHEN status = 'aktiv' THEN monatsrate ELSE 0 END), 0) as total_rate
|
||||
FROM kredite
|
||||
`),
|
||||
|
||||
// Stunden dieses Jahr
|
||||
pool.query(`
|
||||
SELECT
|
||||
COUNT(*) as total_count,
|
||||
COALESCE(SUM(stunden), 0) as total_stunden,
|
||||
COALESCE(SUM(betrag), 0) as total_umsatz
|
||||
FROM stunden
|
||||
WHERE EXTRACT(YEAR FROM datum) = $1
|
||||
`, [currentYear]),
|
||||
|
||||
// Kunden
|
||||
pool.query(`
|
||||
SELECT COUNT(*) as total_count FROM kunden
|
||||
`)
|
||||
]);
|
||||
|
||||
const rechnungen = rechnungenResult.rows[0];
|
||||
const belege = belegeResult.rows[0];
|
||||
const kredite = krediteResult.rows[0];
|
||||
const stunden = stundenResult.rows[0];
|
||||
const kunden = kundenResult.rows[0];
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
year: currentYear,
|
||||
summary: {
|
||||
einnahmen: {
|
||||
total: parseFloat(rechnungen.total) || 0,
|
||||
paid: parseFloat(rechnungen.paid_total) || 0,
|
||||
open: parseFloat(rechnungen.open_total) || 0,
|
||||
count: parseInt(rechnungen.total_count) || 0
|
||||
},
|
||||
ausgaben: {
|
||||
total: parseFloat(belege.total) || 0,
|
||||
count: parseInt(belege.total_count) || 0
|
||||
},
|
||||
kredite: {
|
||||
total: parseInt(kredite.total_count) || 0,
|
||||
active: parseInt(kredite.active_count) || 0,
|
||||
restschuld: parseFloat(kredite.total_restschuld) || 0,
|
||||
monatsrate: parseFloat(kredite.total_rate) || 0
|
||||
},
|
||||
stunden: {
|
||||
total: parseFloat(stunden.total_stunden) || 0,
|
||||
umsatz: parseFloat(stunden.total_umsatz) || 0,
|
||||
count: parseInt(stunden.total_count) || 0
|
||||
},
|
||||
kunden: {
|
||||
total: parseInt(kunden.total_count) || 0
|
||||
},
|
||||
gewinn: (parseFloat(rechnungen.total) || 0) - (parseFloat(belege.total) || 0)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard Summary Error:', error);
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/dashboard/cashflow
|
||||
router.get('/cashflow', async (req, res) => {
|
||||
try {
|
||||
const { months = 6 } = req.query;
|
||||
|
||||
// Monatliche Cashflow-Daten
|
||||
const cashflowData = [];
|
||||
|
||||
for (let i = months - 1; i >= 0; i--) {
|
||||
const date = new Date();
|
||||
date.setMonth(date.getMonth() - i);
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1;
|
||||
|
||||
const [einnahmenResult, ausgabenResult] = await Promise.all([
|
||||
pool.query(`
|
||||
SELECT COALESCE(SUM(betrag), 0) as total
|
||||
FROM rechnungen
|
||||
WHERE EXTRACT(YEAR FROM datum) = $1 AND EXTRACT(MONTH FROM datum) = $2
|
||||
AND status = 'bezahlt'
|
||||
`, [year, month]),
|
||||
pool.query(`
|
||||
SELECT COALESCE(SUM(betrag), 0) as total
|
||||
FROM belege
|
||||
WHERE EXTRACT(YEAR FROM date) = $1 AND EXTRACT(MONTH FROM date) = $2
|
||||
`, [year, month])
|
||||
]);
|
||||
|
||||
const einnahmen = parseFloat(einnahmenResult.rows[0].total) || 0;
|
||||
const ausgaben = parseFloat(ausgabenResult.rows[0].total) || 0;
|
||||
|
||||
cashflowData.push({
|
||||
year,
|
||||
month,
|
||||
monthName: date.toLocaleString('de-DE', { month: 'short' }),
|
||||
einnahmen,
|
||||
ausgaben,
|
||||
saldo: einnahmen - ausgaben
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
cashflow: cashflowData
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard Cashflow Error:', error);
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/dashboard/tax-preview
|
||||
router.get('/tax-preview', async (req, res) => {
|
||||
try {
|
||||
const { year = new Date().getFullYear() } = req.query;
|
||||
|
||||
const [einnahmenResult, ausgabenResult, stundenResult] = await Promise.all([
|
||||
pool.query(`
|
||||
SELECT COALESCE(SUM(betrag), 0) as total
|
||||
FROM rechnungen
|
||||
WHERE EXTRACT(YEAR FROM datum) = $1 AND status = 'bezahlt'
|
||||
`, [year]),
|
||||
pool.query(`
|
||||
SELECT COALESCE(SUM(betrag), 0) as total
|
||||
FROM belege
|
||||
WHERE EXTRACT(YEAR FROM date) = $1
|
||||
`, [year]),
|
||||
pool.query(`
|
||||
SELECT COALESCE(SUM(betrag), 0) as total
|
||||
FROM stunden
|
||||
WHERE EXTRACT(YEAR FROM datum) = $1 AND status = 'bezahlt'
|
||||
`, [year])
|
||||
]);
|
||||
|
||||
const einnahmen = parseFloat(einnahmenResult.rows[0].total) || 0;
|
||||
const ausgaben = parseFloat(ausgabenResult.rows[0].total) || 0;
|
||||
const stundenEinnahmen = parseFloat(stundenResult.rows[0].total) || 0;
|
||||
const gesamtEinnahmen = einnahmen + stundenEinnahmen;
|
||||
const gewinn = gesamtEinnahmen - ausgaben;
|
||||
|
||||
// Vereinfachte Steuerberechnung (nür Hinweis)
|
||||
const estVorschuss = gewinn > 0 ? gewinn * 0.25 : 0; // ca. 25% ESt
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
year: parseInt(year),
|
||||
preview: {
|
||||
einnahmen: {
|
||||
rechnungen: einnahmen,
|
||||
stunden: stundenEinnahmen,
|
||||
gesamt: gesamtEinnahmen
|
||||
},
|
||||
ausgaben,
|
||||
gewinn,
|
||||
estVorschuss: Math.max(0, estVorschuss)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard Tax Preview Error:', error);
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user