from fastapi import APIRouter, Query from backend.db.database import get_connection from backend.services.settings_service import get_income_settings, get_exclude_transfers router = APIRouter(prefix="/api/charts", tags=["charts"]) def _parse_bank_ids(bank_ids: str | None) -> list[int] | None: if not bank_ids or not bank_ids.strip(): return None try: return [int(x.strip()) for x in bank_ids.split(",") if x.strip()] except ValueError: return None @router.get("/income-expense") def income_expense_by_month( year: int | None = Query(None), period_start: str | None = Query(None), period_end: str | None = Query(None), bank_ids: str | None = Query(None), ): """Доход и расход по месяцам. Фильтры: year или period_start/period_end, bank_ids.""" conn = get_connection() try: salary_id = conn.execute("SELECT id FROM banks WHERE is_salary = 1").fetchone() salary_id = salary_id["id"] if salary_id else None only_salary_card, salary_account_id = get_income_settings() exclude_filter = " AND (t.excluded_from_balance = 0 OR t.excluded_from_balance IS NULL)" if get_exclude_transfers(): exclude_filter += " AND t.description NOT LIKE '%Перевод%'" bank_id_list = _parse_bank_ids(bank_ids) bank_filter = "" params = [] if bank_id_list: placeholders = ",".join("?" * len(bank_id_list)) bank_filter = f" AND a.bank_id IN ({placeholders})" params = list(bank_id_list) date_filter = "" if year is not None: date_filter = " AND strftime('%Y', t.operation_date) = ?" params = [str(year)] + params elif period_start or period_end: if period_start: date_filter += " AND t.operation_date >= ?" params.append(period_start) if period_end: date_filter += " AND t.operation_date <= ?" params.append(period_end) rows = conn.execute(""" SELECT strftime('%Y-%m', t.operation_date) as month, SUM(CASE WHEN t.amount > 0 THEN t.amount ELSE 0 END) as debit, SUM(CASE WHEN t.amount < 0 THEN -t.amount ELSE 0 END) as credit, a.bank_id, a.id as account_id FROM transactions t JOIN accounts a ON t.account_id = a.id WHERE 1=1 """ + exclude_filter + bank_filter + date_filter + """ GROUP BY month, a.bank_id, a.id ORDER BY month """, params).fetchall() # Агрегируем по месяцам; доход — по зарплатному банку или только по выбранной карте by_month = {} for r in rows: m = r["month"] if m not in by_month: by_month[m] = {"month": m, "debit": 0.0, "credit": 0.0, "income": 0.0} by_month[m]["debit"] += r["debit"] by_month[m]["credit"] += r["credit"] if only_salary_card and salary_account_id and r["account_id"] == salary_account_id: by_month[m]["income"] += r["debit"] elif not only_salary_card and salary_id and r["bank_id"] == salary_id: by_month[m]["income"] += r["debit"] return list(by_month.values()) finally: conn.close() @router.get("/balance-dynamics") def balance_dynamics( period_start: str | None = Query(None), period_end: str | None = Query(None), bank_ids: str | None = Query(None), ): """Суммарный остаток на конец каждого дня (накопленно). bank_ids — фильтр по банкам.""" conn = get_connection() try: bank_id_list = _parse_bank_ids(bank_ids) if bank_id_list: placeholders = ",".join("?" * len(bank_id_list)) acc_filter = f" AND account_id IN (SELECT id FROM accounts WHERE bank_id IN ({placeholders}))" params = list(bank_id_list) else: acc_filter = "" params = [] transfer_filter = " AND description NOT LIKE '%Перевод%'" if get_exclude_transfers() else "" sql = """ SELECT date(operation_date) as day, SUM(amount) as daily_total FROM transactions WHERE (excluded_from_balance = 0 OR excluded_from_balance IS NULL) """ + transfer_filter + acc_filter if period_start: sql += " AND operation_date >= ?" params.append(period_start) if period_end: sql += " AND operation_date <= ?" params.append(period_end) sql += " GROUP BY day ORDER BY day" rows = conn.execute(sql, params).fetchall() # Накопленный итог cumul = 0.0 result = [] for r in rows: cumul += r["daily_total"] result.append({"date": r["day"], "balance": round(cumul, 2)}) return result finally: conn.close() @router.get("/savings-dynamics") def savings_dynamics( period_start: str | None = Query(None), period_end: str | None = Query(None), bank_ids: str | None = Query(None), ): """Динамика копилки: накопленная сумма операций «Перевод между счетами одного клиента» по дням.""" conn = get_connection() try: bank_id_list = _parse_bank_ids(bank_ids) if bank_id_list: placeholders = ",".join("?" * len(bank_id_list)) acc_filter = f" AND account_id IN (SELECT id FROM accounts WHERE bank_id IN ({placeholders}))" params = ["%Перевод между счетами одного клиента%"] + list(bank_id_list) else: acc_filter = "" params = ["%Перевод между счетами одного клиента%"] transfer_filter = " AND description NOT LIKE '%Перевод%'" if get_exclude_transfers() else "" sql = """ SELECT date(operation_date) as day, SUM(-amount) as daily_savings FROM transactions WHERE description LIKE ? AND (excluded_from_balance = 0 OR excluded_from_balance IS NULL) """ + transfer_filter + acc_filter if period_start: sql += " AND operation_date >= ?" params.append(period_start) if period_end: sql += " AND operation_date <= ?" params.append(period_end) sql += " GROUP BY day ORDER BY day" rows = conn.execute(sql, params).fetchall() cumul = 0.0 result = [] for r in rows: cumul += r["daily_savings"] result.append({"date": r["day"], "savings": round(cumul, 2)}) return result finally: conn.close()