117 lines
5.1 KiB
JavaScript
117 lines
5.1 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const db = require('../../db');
|
|
const { authenticate } = require('../../middleware/auth');
|
|
|
|
const uid = req => req.user.id;
|
|
|
|
// ── Ausgaben CRUD ──────────────────────────────────────────────────────────
|
|
router.get('/expenses', authenticate, (req, res) => {
|
|
const { from, to } = req.query;
|
|
let q = 'SELECT * FROM expenses WHERE user_id=?';
|
|
const p = [uid(req)];
|
|
if (from) { q += ' AND date>=?'; p.push(from); }
|
|
if (to) { q += ' AND date<=?'; p.push(to); }
|
|
q += ' ORDER BY date DESC, id DESC';
|
|
res.json(db.prepare(q).all(...p));
|
|
});
|
|
|
|
router.post('/expenses', authenticate, (req, res) => {
|
|
const { date, category, description, amount } = req.body;
|
|
if (!date || !description || !amount) return res.status(400).json({ error: 'Fehlende Felder' });
|
|
const r = db.prepare('INSERT INTO expenses (user_id,date,category,description,amount) VALUES (?,?,?,?,?)')
|
|
.run(uid(req), date, category||'Sonstiges', description, parseFloat(amount));
|
|
res.json(db.prepare('SELECT * FROM expenses WHERE id=?').get(r.lastInsertRowid));
|
|
});
|
|
|
|
router.delete('/expenses/:id', authenticate, (req, res) => {
|
|
const e = db.prepare('SELECT id FROM expenses WHERE id=? AND user_id=?').get(req.params.id, uid(req));
|
|
if (!e) return res.status(404).json({ error: 'Nicht gefunden' });
|
|
db.prepare('DELETE FROM expenses WHERE id=?').run(e.id);
|
|
res.json({ ok: true });
|
|
});
|
|
|
|
// ── Statistik-Daten ────────────────────────────────────────────────────────
|
|
router.get('/overview', authenticate, (req, res) => {
|
|
const { from, to } = req.query;
|
|
const u = uid(req);
|
|
|
|
let oq = `SELECT o.*,
|
|
COALESCE(o.custom_price,
|
|
(SELECT SUM(oi.custom_price * oi.stueckzahl) FROM order_items oi WHERE oi.order_id=o.id AND oi.custom_price IS NOT NULL)
|
|
) as revenue,
|
|
(SELECT COALESCE(SUM(COALESCE(c.preis_freundschaft, oi.preis_freundschaft, 0) * oi.stueckzahl),0)
|
|
FROM order_items oi LEFT JOIN calculations c ON c.id=oi.calculation_id WHERE oi.order_id=o.id) as base_cost
|
|
FROM orders o WHERE o.user_id=?`;
|
|
const op = [u];
|
|
if (from) { oq += ' AND DATE(o.created_at)>=?'; op.push(from); }
|
|
if (to) { oq += ' AND DATE(o.created_at)<=?'; op.push(to); }
|
|
oq += ' ORDER BY o.created_at DESC';
|
|
const orders = db.prepare(oq).all(...op);
|
|
|
|
const paidOrders = orders.filter(o => !!o.bezahlt);
|
|
const openOrders = orders.filter(o => !o.bezahlt && o.status !== 'warteliste');
|
|
|
|
const totalRevenue = paidOrders.reduce((s,o) => s + (o.revenue || 0), 0);
|
|
const openRevenue = openOrders.reduce((s,o) => s + (o.revenue || 0), 0);
|
|
const totalBaseCost = paidOrders.reduce((s,o) => s + (o.base_cost || 0), 0);
|
|
const totalProfit = totalRevenue - totalBaseCost;
|
|
|
|
let eq = 'SELECT * FROM expenses WHERE user_id=?';
|
|
const ep = [u];
|
|
if (from) { eq += ' AND date>=?'; ep.push(from); }
|
|
if (to) { eq += ' AND date<=?'; ep.push(to); }
|
|
const expenses = db.prepare(eq + ' ORDER BY date DESC').all(...ep);
|
|
const totalExpenses = expenses.reduce((s,e) => s + e.amount, 0);
|
|
const netProfit = totalProfit - totalExpenses;
|
|
|
|
const byStatus = { warteliste:0, in_arbeit:0, fertig:0, bezahlt:0, abgeschlossen:0 };
|
|
for (const o of orders) {
|
|
if (!!o.bezahlt && !!o.abgeholt) byStatus.abgeschlossen++;
|
|
else if (!!o.bezahlt) byStatus.bezahlt++;
|
|
else if (o.status in byStatus) byStatus[o.status]++;
|
|
}
|
|
|
|
const byCategory = {};
|
|
for (const e of expenses) {
|
|
byCategory[e.category] = (byCategory[e.category] || 0) + e.amount;
|
|
}
|
|
|
|
// Monatliche Daten
|
|
const monthlyRev = db.prepare(`
|
|
SELECT strftime('%Y-%m', created_at) as month,
|
|
SUM(CASE WHEN bezahlt=1 THEN COALESCE(custom_price,0) ELSE 0 END) as revenue,
|
|
COUNT(*) as orders
|
|
FROM orders WHERE user_id=? GROUP BY month ORDER BY month
|
|
`).all(u);
|
|
|
|
const monthlyExp = db.prepare(`
|
|
SELECT strftime('%Y-%m', date) as month, SUM(amount) as expenses
|
|
FROM expenses WHERE user_id=? GROUP BY month ORDER BY month
|
|
`).all(u);
|
|
|
|
const monthlyMap = {};
|
|
for (const m of monthlyRev) monthlyMap[m.month] = { month:m.month, revenue:m.revenue||0, orders:m.orders||0, expenses:0 };
|
|
for (const m of monthlyExp) {
|
|
if (monthlyMap[m.month]) monthlyMap[m.month].expenses = m.expenses||0;
|
|
else monthlyMap[m.month] = { month:m.month, revenue:0, orders:0, expenses:m.expenses||0 };
|
|
}
|
|
const monthlyData = Object.values(monthlyMap).sort((a,b)=>a.month.localeCompare(b.month)).slice(-12);
|
|
|
|
let cum = 0;
|
|
const cumulativeData = monthlyData.map(m => {
|
|
cum += (m.revenue||0) - (m.expenses||0);
|
|
return { month: m.month, value: Math.round(cum*100)/100 };
|
|
});
|
|
|
|
res.json({
|
|
totalRevenue, openRevenue, totalBaseCost, totalProfit,
|
|
totalExpenses, netProfit, totalOrders: orders.length,
|
|
byStatus, byCategory, monthlyData, cumulativeData,
|
|
expenses: expenses.slice(0,100),
|
|
recentOrders: orders.slice(0,30),
|
|
});
|
|
});
|
|
|
|
module.exports = router;
|