import { type Express } from "express"; import { query } from "./db-client"; import { getTableConfig } from "./table-config"; import { authenticateToken, requirePermission, type AuthenticatedRequest } from "../middleware/auth.middleware"; import * as cache from "./cache"; export function registerTimeseriesRoutes(app: Express): void { app.get("/api/income/tree-timeseries", authenticateToken, requirePermission('finance.manage'), async (req: AuthenticatedRequest, res) => { await handleTreeTimeseries(req, res, "income"); }); app.get("/api/expenses/tree-timeseries", authenticateToken, requirePermission('finance.manage'), async (req: AuthenticatedRequest, res) => { await handleTreeTimeseries(req, res, "expense"); }); } async function handleTreeTimeseries(req: AuthenticatedRequest, res: any, type: "income" | "expense") { try { const config = getTableConfig(); const dateFrom = req.query.dateFrom as string; const dateTo = req.query.dateTo as string; const groupBy = (req.query.groupBy as string) || "month"; const categoryId = req.query.categoryId as string | undefined; const cacheKey = `${type}:timeseries:${dateFrom}:${dateTo}:${groupBy}:${categoryId}`; const cached = cache.get(cacheKey); if (cached) { res.json(cached); return; } const table = type === "income" ? config.incomeTable : config.expenseTable; const sumCol = type === "income" ? config.incomeSumColumn : config.expenseSumColumn; const dateFilter = []; const params: any[] = []; if (dateFrom) { dateFilter.push(`"data" >= $${params.length + 1}`); params.push(dateFrom); } if (dateTo) { dateFilter.push(`"data" <= $${params.length + 1}`); params.push(dateTo); } const where = dateFilter.length > 0 ? `WHERE ${dateFilter.join(" AND ")}` : ""; const dateTrunc = groupBy === "year" ? "year" : groupBy === "quarter" ? "quarter" : "month"; const result = await query( `SELECT DATE_TRUNC('${dateTrunc}', "data"::date) as period, SUM("${sumCol}") as total, COUNT(*) as count FROM "${table}" ${where} GROUP BY period ORDER BY period`, params ); const periods = result.rows.map((r: any) => { const d = new Date(r.period); if (groupBy === "year") return `${d.getFullYear()}`; if (groupBy === "quarter") return `${d.getFullYear()}-Q${Math.floor(d.getMonth() / 3) + 1}`; return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`; }); const data = result.rows.map((r: any) => parseFloat(r.total) || 0); const response = { success: true, periods, series: [{ id: "total", name: type === "income" ? "Доходы" : "Расходы", data, }], }; cache.set(cacheKey, response); res.json(response); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } }