Files

43 KiB
Raw Permalink Blame History

Budget App

Персональный бюджет в виде веб-приложения. Замена Excel-таблицы ~/Downloads/Budget.xlsx, в которой ведутся транзакции 2020–2024+ по нескольким счетам в разных валютах.

Solo на старте (только Alex), но код пишем generic — с прицелом на public SaaS позже (multi-tenant, конфиги отделены).

Источник: текущая Excel-таблица

Файл: ~/Downloads/Budget.xlsx (3.6 MB, 13 листов).

Листы

Лист Назначение
транзакции Главное хранилище. ~34 462 строки, 28 колонок (A–AB). Все дебеты/кредиты по всем счетам подряд.
курсы Справочник курсов: Currency, Rate, Date, computed-курс к выбранной валюте отчёта. До 9 467 строк (история по датам).
категории Иерархия категорий: подкатегория → родитель с эмодзи (например Аренда🏢 Аренда).
счета Справочник счетов: имя, валюта, активный (bool), льготный период, месячный платёж, курс к выбранной, name in form.
2020 отчет2024 отчет Годовые сводки: план/факт по доходам, расходам, инвестициям, разнице, накопленному капиталу.
налоги 22-24 Учёт ИП-патентов, НДФЛ и пр.
сводная Сводная таблица по годам.
Наличные (форма) Hidden — форма быстрого ввода.
test Песочница.

Схема листа «транзакции»

Колонки (расшифрованы из shared strings + формул):

Кол Имя Тип Описание
A дата date Дата операции.
B сумма number Сумма в валюте счёта-источника.
C дебет ref→счета Счёт, с которого ушло (источник). Пусто = доход извне.
D кредит ref→счета Счёт, на который пришло (получатель). Пусто = расход вовне.
E категория ref→категории Подкатегория (см. лист категории).
F комментарий text Свободный комментарий.
G курс number Кросс-курс дебет→кредит, если перевод между валютами.
H входящ деб number Остаток на дебете до операции.
I остаток деб number Остаток на дебете после.
J входящ кред formula =IF(D<>"", B*IF(C<>"", G, 1), "") — сумма в валюте кредита.
K остаток кред number Остаток на кредите до.
L остаток кред formula =IF(D<>"", K+J, "") — остаток на кредите после.
M вал. счёта formula VLOOKUP(C, счета, 2) — валюта дебета.
N деб: курс к тек number Курс валюты дебета к выбранной валюте отчёта.
O сумма в тек вал number Сумма в валюте отчёта (по курсу дебета).
P Скрытая.
Q крд: курс к тек number Курс валюты кредита к выбранной валюте отчёта.
R сумма в тек number Сумма в валюте отчёта (по курсу кредита).
S Налог number Сумма налога по этой операции.
T Курс number Курс на дату операции.
UAB Резерв / служебные.

Семантика двойной записи: одна строка = одно перемещение средств. Если C (дебет) пуст — это вход денег извне (доход). Если D (кредит) пуст — это выход вовне (расход). Если оба заполнены — внутренний перевод (между своими счетами, возможно с конверсией валют через G).

Категории (примеры родительских групп)

💰 Salary, 💸 Комиссии, 📟 Квартплата/Связь, 🧸 Дети, 🏢 Аренда, ⛽️ Транспорт/АЗС, 💊 Медицина, 🛒 Grocery, 🍣 Рестораны, 🧱 Стройка, 💷 Кредиты, 🏖️ Отдых, 💸 Налоги, 🛠️ Быт/Техника, 🚙 Авто, 👚 Одежда, 💰 Дивиденды, 💰 Депозиты.

Подкатегории — конкретные траты (Кафе и рестораны, АЗС, Кровля материалы, Salary 88 05.11, ...).

Счета (примеры)

Demir KGS (Сом), Нал USD (Доллар), Альфа (Рубль), Нал RUB (Рубль), Альфа Кредитка, Сбер, Demir ИП USD, Нал EUR (Евро), и т.д.

Валюты в обращении: RUB, USD, EUR, KGS, KZT, UZS, AED, CNY.

Что таблица умеет уже сейчас

  1. Учёт транзакций в любой валюте, с автопересчётом в выбранную валюту отчёта.
  2. Остатки по счетам в реальном времени (через остаток деб / остаток кред).
  3. Категоризация в иерархии (родитель/подкатегория) с эмодзи.
  4. Годовые отчёты: план vs факт, доход/расход/инвестиции, % отложено, накопленный капитал, долг.
  5. Курсы валют с историей (за каждую дату — свой курс).
  6. Налоговый учёт (отдельный лист налоги 22-24).
  7. Сводная — динамика по годам.

Болевые точки таблицы

  • Excel на 34k+ строк уже тормозит на пересчёте формул (VLOOKUP на массивы, IF-цепочки в каждой строке).
  • Ввод транзакций — ручной, через скрытую форму «Наличные», нет автоимпорта банковских выписок.
  • Категоризация — ручная.
  • Невозможно нормально работать с мобильного.
  • Анализ ограничен сводными в Excel: нет нормальных графиков по подкатегориям, кросс-фильтрации, прогнозов.
  • Нет ничего про Freedom Gap / инвестиции: нет трекинга портфеля, нормы сбережений, проекции "когда аренда + проекты покроют расходы".
  • История курсов хранится в строках листа курсы — это не нормализованная таблица.
  • Нет API: нельзя интегрировать с банковскими экспортами, нельзя автоматически тянуть в дашборд.

Концепция веб-приложения

Цель

Заменить Excel приложением, которое:

  1. Принимает все те же транзакции (multi-currency, multi-account, double-entry).
  2. Считает аналитику и сводки на лету через SQL, а не через VLOOKUP.
  3. Импортирует банковские выписки (Альфа, Сбер, Demir, …) автоматически + AI-резолвер для неоднозначных случаев.
  4. Даёт мобильный UI для быстрого ввода трат на ходу.
  5. Freedom Gap — разница между расходами и пассивным/полупассивным доходом (аренда, дивиденды, проекты). График прогресса к нулевому gap. What-if сценарии: "если проект даёт X, аренда Y — через N лет свобода".
  6. Прогнозирует траты на N месяцев вперёд по сезонной модели (категории 🧱 Стройка, 🏖️ Отдых цикличны).

Доменная модель (первая итерация)

User (id, email, ...)                            -- для будущего multi-tenant SaaS
Settings (user_id, base_currency_id, ...)        -- base currency выбирается в настройках
Currency (code, name, symbol)
Account (id, user_id, name, currency_id, type {cash, debit, credit, deposit, brokerage}, active, credit_limit, grace_period_days, monthly_payment)
CategoryGroup (id, user_id, name, emoji)         -- родитель
Category (id, user_id, name, group_id)           -- подкатегория
ExchangeRate (currency_id, date, rate_to_base, source {nbkr, manual, ...})  -- история по датам
Transaction (
  id, user_id, date, amount,
  source_account_id NULL,                        -- дебет (откуда); NULL = доход извне
  dest_account_id NULL,                          -- кредит (куда); NULL = расход вовне
  cross_rate NULL,                               -- курс при internal transfer
  category_id, comment, tax_amount NULL,
  import_id NULL                                 -- ссылка на BankImport
)
TaxRecord (year, month, type {patent, ндфл, …}, amount, currency_id, paid_date NULL)
BankImport (id, user_id, account_id, source {alfa, sber, demir, ...}, fetched_at, raw_blob, status {pending, parsed, resolved, manual})
Alert (id, user_id, type {manual_resolve_needed, parse_failed, ...}, payload, sent_at, ack_at NULL)  -- TG-уведомления
FireGoal (id, user_id, target_amount, target_currency_id, expected_return_rate, target_date NULL)
InvestmentSnapshot (date, account_id, value, currency_id)

Base currency не хардкодится — выбирается в Settings, и все пересчёты (остатки, отчёты, графики) автоматически приводятся к ней по ExchangeRate.rate_to_base на дату.

Архитектура

  • Backend: Python + FastAPI + SQLAlchemy + Postgres.
  • Frontend: Vue 3 + Vite + Pinia. Адаптив для мобильного (PWA).
  • БД: отдельная БД budget_app в существующем кластере Postgres на Eagle (тот же инстанс, что и personal_os — не отдельный контейнер).
  • Деплой: Docker Compose на Mac (Eagle). Конфиги в ~/docker/budget-app/ (по конвенции остальных сервисов). Код проекта — ~/Developer/budget-app/ с git.
  • Внешний доступ: budget.qentra.top через Cloudflare Tunnel (или существующий nginx-reverse-proxy). Auth — login/password + долгоживущий JWT-токен в localStorage/cookie. Один пользователь на старте, но схема готова под multi-tenant.
  • Импорт Excel: однократный скрипт миграции (scripts/import-xlsx.py), читает Budget.xlsx, заливает в БД.
  • Курсы валют: cron-джоб тянет курсы с НБ Кыргызстана (nbkr.kg XML feed) по расписанию (раз в день). Если на какую-то дату курса нет — автоматически дозаполняет (back-fill ближайшего рабочего дня). Источник по умолчанию для KGS-базы; для других валют — ручной override или дополнительные источники (ЦБ РФ, ECB).
  • Импорт банков: автоматический fetch выписок (где есть API/HTML-парсинг) + парсеры CSV/XLS на Python (Альфа, Сбер, Demir — у каждого свой формат). После парсинга — прогон через AI-агента (LLM) для резолва неоднозначных строк: категоризация, merge дубликатов, идентификация контрагентов.
  • Telegram-бот для алертов: оповещение, когда нужно ручное вмешательство (AI не смог однозначно категоризировать, парсер сломался на новом формате выписки, обнаружен дубль). Подтверждение/правка через инлайн-кнопки в TG → апдейт в БД.
  • HTTP MCP-интеграция: приложение экспонирует MCP-сервер (HTTP transport), чтобы Eagle/Hermes мог запрашивать сводки, добавлять транзакции голосом, дёргать прогнозы из чата.
  • Аналитика: SQL-агрегаты + Apache ECharts на фронте.
  • FIRE / Инвестпортфели: отдельная фаза после того, как бюджетирование готово. Не в MVP.

Этапы

  1. Phase 0 — Discovery & schema. Полностью разобрать Excel, утвердить доменную модель. (в процессе — этот док)
  2. Phase 1 — Skeleton + import. Создать ~/Developer/budget-app/ (git init), FastAPI + Vue в Docker Compose в ~/docker/budget-app/ с подключением к существующему Postgres-кластеру на хосте (новая БД budget_app). Миграция xlsx → БД. Read-only viewer транзакций + остатков + годовые отчёты. Внешний доступ через budget.qentra.top с auth (FastAPI Users + JWT).
  3. Phase 2 — Полноценный Viewer + CRUD. Довести до уровня Excel по функциональности:
    • Валюта у счетов (символ)
    • Категории с иерархией (группа → подкатегория)
    • Свёртка по годам/месяцам (drill-down как в Excel)
    • Dynamic scrolling (infinite scroll вместо кнопок пагинации)
    • CRUD транзакций: добавление, редактирование, удаление
    • CRUD категорий
    • CRUD счетов
    • Ручная правка курсов
    • Дашборды: расход по категориям, динамика, бёрндаун по бюджету
    • Прогнозы (сезонная модель по категориям)
    • Multi-currency: отображение балансов в валюте счёта + в base currency
    • Сводная таблица по годам (как лист сводная в Excel)
    • Налоговый учёт (как лист налоги 22-24 в Excel)
  4. Phase 3 — Импорт банков + AI-резолвер. Автофетч/парсеры (Альфа, Сбер, Demir). LLM-резолвер. TG-бот для ручных подтверждений.
  5. Phase 4 — Mobile / PWA. Быстрый ввод с телефона.
  6. Phase 5 — MCP HTTP. Экспонировать MCP-эндпоинт для Hermes.
  7. Phase 6 — Freedom / Инвестиции. Замена классического FIRE на Freedom Gap — разница между расходами и пассивным доходом.
    • P0: Savings Rate Dashboard (норма сбережений из транзакций)
    • P0: Multi-Currency Net Worth (общий капитал USD/KGS)
    • P1: Investment Snapshots (ручной ввод раз в месяц, таблица + график)
    • P1: Freedom Gap Dashboard (доход-расход-аренда-проект = gap)
    • P1: Passive Income Tracker (аренда + дивиденды + депозиты)
    • P2: Freedom Goal with Projection Engine (what-if сценарии)
    • P3: What-if Simulator (слайдеры: курс, аренда, проект, норма сбережений)
    • Новые таблицы: investment_snapshot, freedom_goal
    • Новые страницы: /freedom, /finances
    • Не делать: Monte Carlo, Withdrawal Strategy Planner, Roth Conversion Ladder — не применимы

Multi-tenant readiness (для будущего public SaaS)

Даже в solo-режиме на старте:

  • Все таблицы доменной модели имеют user_id (на solo — захардкожен на одного юзера).
  • Конфиги (домен, секреты, базовая валюта) — в .env / config-файле, не в коде.
  • AI-промпты, парсеры банков, источники курсов — pluggable, через интерфейсы/registry.
  • Никаких личных данных Alex в коде (имена счетов, категорий, комментарии) — всё через миграцию данных в БД.
  • Лицензия — выбрать перед публичным релизом (AGPL/MIT/коммерческая).

Открытые вопросы

Phase 0 закрыт — ключевые решения зафиксированы выше (auth, БД, raw blobs, импорт, multi-tenant readiness). Вопросы Phase 4 (AI-провайдер для резолвера, fallback на локальную LLM) — отложены до старта той фазы.

Phase 1 — Skeleton + import (детальный план)

Цель фазы

К концу Phase 1:

  • FastAPI + Vue запущены в Docker Compose на Eagle.
  • Все ~34 462 транзакции из Budget.xlsx залиты в БД budget_app (в существующем кластере Postgres на хосте).
  • По адресу https://budget.qentra.top (Cloudflare Tunnel) — login → read-only список транзакций, остатки по счетам, годовые отчёты.
  • Auth: FastAPI Users + JWT, один пользователь (Alex), долгоживущий токен (90 дней).
  • Никакого CRUD пока — только просмотр. CRUD это Phase 3.

Структура репозитория ~/Developer/budget-app/

budget-app/
├── .env.example                       # шаблон конфига
├── .gitignore
├── README.md
├── docker-compose.yml                 # симлинк → ~/docker/budget-app/
├── backend/
│   ├── pyproject.toml                 # uv + ruff
│   ├── alembic.ini
│   ├── alembic/versions/              # миграции
│   ├── src/budget/
│   │   ├── __init__.py
│   │   ├── main.py                    # FastAPI app entry
│   │   ├── config.py                  # pydantic-settings из .env
│   │   ├── db.py                      # SQLAlchemy async engine
│   │   ├── auth/                      # FastAPI Users
│   │   │   ├── models.py
│   │   │   ├── schemas.py
│   │   │   └── routes.py
│   │   ├── domain/                    # SQLAlchemy модели
│   │   │   ├── user.py
│   │   │   ├── settings.py
│   │   │   ├── currency.py
│   │   │   ├── account.py
│   │   │   ├── category.py
│   │   │   ├── exchange_rate.py
│   │   │   └── transaction.py
│   │   ├── api/                       # read-only роуты
│   │   │   ├── transactions.py
│   │   │   ├── accounts.py
│   │   │   └── reports.py
│   │   └── importers/
│   │       └── xlsx_import.py
│   └── tests/                         # pytest
├── frontend/
│   ├── package.json                   # pnpm + Vue 3 + Vite + Pinia
│   ├── vite.config.ts
│   ├── tsconfig.json
│   ├── index.html
│   └── src/
│       ├── main.ts
│       ├── App.vue
│       ├── router.ts
│       ├── stores/auth.ts
│       ├── api/client.ts              # axios + JWT interceptor
│       ├── pages/
│       │   ├── Login.vue
│       │   ├── Transactions.vue
│       │   ├── Accounts.vue
│       │   └── Reports.vue
│       └── components/
└── scripts/
    ├── import-xlsx.py                 # one-shot миграция
    └── create-admin.sh                # bootstrap первого юзера

Docker Compose (~/docker/budget-app/docker-compose.yml)

Три сервиса:

  • backend — FastAPI на uvicorn, порт 8400 (внутренний). Подключается к Postgres хоста через host.docker.internal:5432.
  • frontend — nginx со статикой собранного Vite-билда, порт 8401. Проксирует /api/* на backend:8400.
  • tunnelcloudflare/cloudflared, exposes budget.qentra.topfrontend:80.

Volumes:

  • ./data/imports//data/imports/ (для будущих банковских raw blobs).
  • ~/Downloads/Budget.xlsx (read-only) → /data/import/Budget.xlsx (для миграции).

.env (через .env.example в репо)

# DB — подключение к существующему кластеру Postgres на хосте
DB_HOST=host.docker.internal
DB_PORT=5432
DB_NAME=budget_app
DB_USER=budget_app
DB_PASSWORD=...

# Auth
JWT_SECRET=...                         # openssl rand -hex 32
JWT_LIFETIME_SECONDS=7776000           # 90 дней (долгоживущий)
ADMIN_EMAIL=alex@...
ADMIN_PASSWORD=...                     # для первого юзера через create-admin.sh

# Settings
BASE_CURRENCY=KGS                      # дефолт, меняется в UI

# Cloudflare Tunnel
CLOUDFLARE_TUNNEL_TOKEN=...

Доменные модели для Phase 1 (минимум, чтобы залить xlsx и показать read-only)

Таблица Поля для MVP
user id, email, hashed_password, is_active, is_superuser (FastAPI Users)
settings id, user_id, base_currency_code
currency code (PK), name, symbol
account id, user_id, name, currency_code, type, active, credit_limit?, grace_period_days?, monthly_payment?
category_group id, user_id, name, emoji
category id, user_id, name, group_id
exchange_rate currency_code, date, rate_to_base, source — PK по (currency_code, date, source)
transaction id, user_id, date, amount, source_account_id?, dest_account_id?, cross_rate?, category_id?, comment, tax_amount?, import_id? (NULL для xlsx)

Опускаем в Phase 1: tax_record, bank_import, alert, fire_goal, investment_snapshot — приедут в следующих фазах.

Шаги Phase 1 (порядок исполнения)

  1. Init репо: ~/Developer/budget-app/ + git init, .gitignore (python/node/docker/.env), README со ссылкой на этот док в vault.
  2. Backend skeleton: uv init, FastAPI app, конфиг через pydantic-settings, healthcheck /api/health.
  3. Postgres bootstrap на Eagle: создать БД и юзера в существующем кластере —
    CREATE DATABASE budget_app;
    CREATE USER budget_app WITH PASSWORD '...';
    GRANT ALL ON DATABASE budget_app TO budget_app;
    
    Проверить, что кластер слушает host.docker.internal (правка postgresql.conf listen_addresses + pg_hba.conf если нужно).
  4. Alembic init + initial миграция: SQLAlchemy модели → таблицы.
  5. FastAPI Users: подключить, роуты /api/auth/jwt/login, /api/auth/register (после bootstrap закрываем), /api/users/me.
  6. Bootstrap первого юзера: scripts/create-admin.sh создаёт Alex из .env.
  7. Импортёр xlsx (scripts/import-xlsx.py):
    • Читает ~/Downloads/Budget.xlsx через openpyxl (fallback на lxml если timeout'ы).
    • Парсит листы: курсыexchange_rate, категорииcategory_group/category, счетаaccount, транзакцииtransaction.
    • Все записи привязываются к user_id Alex.
    • Идемпотентно: повторный запуск с тем же файлом не дублирует (hash по date+amount+source+dest+comment).
  8. Read-only API:
    • GET /api/transactions?from=&to=&category=&account=&page= — пагинация.
    • GET /api/accounts — счета с балансами (в валюте счёта + в base currency).
    • GET /api/reports/yearly?year= — годовая сводка (план/факт по группам).
  9. Frontend skeleton: pnpm create vite, Vue 3 + TS + Pinia, layout (header/sidebar/main), Login страница, JWT в localStorage.
  10. Read-only страницы:
    • Transactions: таблица с пагинацией, фильтры по дате/категории/счёту, поиск по комментарию.
    • Accounts: список с текущими балансами в валюте счёта + в base currency.
    • Reports: годовые сводки 2020–2024 по группам категорий, % сбережений.
  11. Docker Compose: написать в ~/docker/budget-app/, билдить backend/frontend, прокинуть host.docker.internal для Postgres.
  12. Cloudflare Tunnel: создать DNS-запись budget.qentra.top → tunnel ID, прописать ingress в cloudflared конфиге.
  13. Smoke-тест:
    • https://budget.qentra.top → login → видишь все ~34k транзакций.
    • GET /api/accounts возвращает все счета с балансами, совпадающими с Excel (±погрешность округления).
    • GET /api/reports/yearly?year=2024 совпадает с листом 2024 отчет.

Зависимости backend

  • fastapi, uvicorn[standard], pydantic-settings
  • sqlalchemy[asyncio], asyncpg, alembic
  • fastapi-users[sqlalchemy]
  • openpyxl (xlsx-импорт)
  • httpx (заготовка для cron курсов в Phase 2)
  • python-multipart, passlib[bcrypt]
  • dev: pytest, pytest-asyncio, ruff

Зависимости frontend

  • vue, vue-router, pinia
  • axios (JWT interceptor)
  • @vueuse/core
  • echarts (скаффолд под Phase 5, не используем активно)
  • dev: vite, typescript, eslint, prettier

Правила работы

  1. Тесты — обязательны для каждого нового API-роута или изменения. Если код не покрыт тестом — он не готов.
  2. Обновление доку — после каждой завершённой задачи обновлять таблицу прогресса и Acceptance criteria в этом доке.
  3. Комит — после каждой логически завершённой задачи (не раз в 10 шагов).

Acceptance criteria Phase 1

  • https://budget.qentra.top открывается, login работает.
  • Все ~34 462 строки транзакций видны в UI с пагинацией.
  • Балансы счетов совпадают с Excel (по последней транзакции каждого счёта).
  • Годовые отчёты 2020–2024 совпадают с Excel-листами.
  • Код в ~/Developer/budget-app/, БД budget_app в существующем Postgres, Compose в ~/docker/budget-app/.
  • Никаких хардкод-значений (имена счетов, категорий, комментарии) в коде — всё через xlsx-миграцию.

Риски и нюансы

  • openpyxl на 34k строк: может быть медленно (>30 сек). Acceptable — миграция одноразовая. Fallback: прямой парс XML через lxml.
  • Cloudflare Tunnel: первый раз нужен ручной шаг в Cloudflare dashboard (создать tunnel, получить токен).
  • Долгоживущий JWT (90 дней): обычно не рекомендуется, но это явное требование. Документируем в README.
  • host.docker.internal + Postgres: на macOS Postgres из brew обычно слушает только localhost. Нужно проверить и поправить listen_addresses = '*' в postgresql.conf + добавить запись в pg_hba.conf для подсети Docker (172.17.0.0/16 / 172.18.0.0/16).
  • Сетка Docker → DDG VPN: budget-app не нуждается в доступе к DDG-сетям, обычный bridge ок.

Следующий шаг

Старт Phase 1 — шаг 1 (init репо ~/Developer/budget-app/).


Создано: 2026-06-21 Статус: Phase 1 — в работе

Phase 1 progress

Шаг Статус Кем
Init репо Орёл
Backend skeleton Орёл
Postgres bootstrap — роль + БД Кит
Alembic initial migration Кит
FastAPI Users + auth routes Кит
Bootstrap первого юзера Кит
XLSX импортёр (6 045 транзакций) Кит
Read-only API (12 тестов) Кит
Frontend skeleton + read-only страницы Кит
Docker Compose (2 сервиса, работает) Кит
Cloudflare Tunnel budget.qentra.top Alex
Сверка данных Кит

Disaster recovery: CASCADE data loss

Сценарий: Удалён пользователь (A-click → user delete). Из-за ON DELETE CASCADE на transaction_user_id_fkey все транзакции этого пользователя удалены мгновенно (6 045 строк). Балансы обнулены.

Recovery шаги (на будущее)

# 1. Пересоздать пользователя с тем же email (admin123)
curl -XPOST .../api/auth/register -H... -d'{"email":"alex@qentra.top","password":"admin123"}'

# 2. Переимпортировать транзакции из xlsx
cd ~/Developer/budget-app
uv run python src/budget/importers/__init__.py

# 3. Проверить балансы — все 18 счетов должны совпасть с excel-balances.md
# 4. Пересобрать и передеплоить backend (дата формат) + frontend (любые изменения)
docker-compose build backend && docker-compose up -d backend
cd frontend && npm run build && cd .. && docker-compose build --no-cache frontend && docker-compose up -d frontend

Формат даты на фронте (актуальный)

API возвращает t.date.isoformat()2026-05-29T17:00:00. Фронт режет: {{ t.date.slice(0, 10) }} {{ t.date.slice(11, 16) }}2026-05-29 17:00.

Если время = 00:00 — в Excel не было времени для этой транзакции. Это корректно.

Символы валют вместо колонки

Колонка "Валюта" убрана из таблиц Transactions, Accounts, TaxRecords. Вместо неё символ валюты показывается непосредственно перед суммой (Transactions, TaxRecords) или в ячейке (Accounts).

Маппинг на фронте (постоянный, не из БД):

Код Символ Валюта
USD $ Доллар
EUR Евро
RUB Рубль
KGS С̲ Сом (с с нижней чертой)
KZT Тенге (уже есть в Unicode)

Файлы: frontend/src/pages/Transactions.vue, Accounts.vue, TaxRecords.vue — каждая содержит CURRENCY_SYMBOLS маппинг и функцию getCurrencySymbol/currencySymbol.

БД: исправление типа кредиток

Четыре кредитных счета были ошибочно импортированы как type='debit' вместо type='credit'. Исправлено вручную:

UPDATE account SET type = 'credit' WHERE name ILIKE '%кредитка%';

Затронуты: Альфа Кредитка, Сбер Кредитка, ВТБ Кредитка, Тинькофф Кредитка. Модель AccountType поддерживает credit с самого начала — это была неточность импорта из Excel.

Страница быстрого ввода наличных — CashInput (2026-06-23)

Файл: frontend/src/pages/CashInput.vue Маршрут: /cash Хедер: скрыт на этой странице (App.vue проверяет route.name === 'CashInput')

Одна страница с двумя режимами, переключаемыми сегмент-кнопкой.

Режим «Расход» (по умолчанию):

  • Выбор валюты — крупные кнопки с символами ($ ₽ € С̲ ₸). Последняя выбранная валюта сохраняется в localStorage (cash_last_currency).
  • Выбор счёта — после выбора валюты показываются все счета в этой валюте (не только cash). При смене валюты — авто-выбирается первый наличный (type=cash) счёт. После добавления транзакции — сбрасывается обратно на первый наличный.
  • Сумма (крупное поле)
  • Чипы топ-категорий (15 штук)
  • Комментарий (опционально)
  • is_placeholder: если выбранный счёт type=cash → обычная транзакция (is_placeholder=false). Если debit/credit → is_placeholder=true (предварительная, будет заменена при импорте выписки).

Режим «Обмен валюты»:

  • Выбор валюты-источника → счёт-источник
  • Выбор валюты-получателя → счёт-получатель
  • Три поля с двусторонним пересчётом (сумма, курс, получу)
  • Комментарий (предзаполнен)

Поле is_placeholder

Добавлено в модель Transaction:

  • is_placeholder: Boolean (default false)
  • Alembic migration: add_is_placeholder_to_transaction
  • API: POST /api/transactions принимает is_placeholder, GET /api/transactions фильтрует по is_placeholder
  • Назначение: помечать предварительные транзакции (ручной ввод расходов с debit/credit счетов), которые позже будут заменены реальными данными из банковской выписки (матчинг по сумме, дате, счёту).

Пароль

  • admin123 — совпадает с ADMIN_PASSWORD в .env

Типы счетов

Тип Описание
cash Наличные (Нал KGS, USD, RUB, EUR, KZT, AED, UZS)
debit Дебетовые карты и счета (Альфа, Сбер, Demir и т.д.)
credit Кредитные карты (Альфа Кредитка, Сбер Кредитка, ВТБ Кредитка, Тинькофф Кредитка)
deposit Депозиты (не используется)
brokerage Брокерские счета (не используется)

В Phase 1 кредитки были ошибочно импортированы как debit — исправлено на credit прямым SQL UPDATE.

Phase 2 progress

Шаг Статус Кем
Валюта у счетов (символ) Кит
Категории с иерархией (API + фронт) Кит
Фронт: формы CRUD (транзакции, категории, счета) Кит
Свёртка по годам/месяцам (drill-down) Кит
Dynamic scrolling (infinite scroll) Кит
CRUD транзакций (API + тесты) Кит
CRUD категорий (API + тесты) Кит
CRUD счетов (API + тесты) Кит
Ручная правка курсов + API Кит
Дашборды (расход по категориям, динамика, сводная) Кит
Multi-currency отображение (баланс в валюте счёта + base currency) Кит
Сводная по годам Кит
Налоговый учёт Кит
DateTime в транзакциях (date → DateTime, datetime-local на фронте, миграция) Кит
Символ валюты вместо колонки (Transactions, Accounts, TaxRecords) Кит
Исправлен тип кредиток (debit → credit) Кит
Страница быстрого ввода наличных / обмена валюты (CashInput.vue) Кит
is_placeholder в Transaction (placeholder для не-cash расходов) Кит
CashInput: выбор по валюте, is_placeholder для debit/credit Кит

Phase 2 — что сделано (подробно)

API (новые эндпоинты):

  • GET /api/reports/monthly?year= — помесячная разбивка доходов/расходов
  • GET /api/reports/summary — сводная по годам
  • GET /api/reports/category-breakdown?year=&month= — расходы по группам категорий (данные для дашборда)
  • GET/POST/PUT/DELETE /api/exchange-rates — CRUD курсов валют
  • GET /api/exchange-rates/summary — группировка по типу налога
  • GET/POST/PUT/DELETE /api/tax-records — CRUD налоговых записей
  • Accounts API теперь возвращает balance_in_base и base_currency (мультивалютность)

БД:

  • Новая таблица tax_record (alembic migration)

Фронтенд (новые страницы):

  • /dashboard — дашборд с помесячной динамикой (CSS-chart), расходами по категориям, сводной по годам
  • /exchange-rates — таблица курсов с фильтрами, CRUD через модалку
  • /tax-records — таблица налогов с фильтрами и сводкой по типам
  • /cashCashInput.vue — мобильная страница быстрого ввода наличных расходов и обмена валюты (два режима, чипы категорий, сегмент-кнопка, двусторонний пересчёт курса/суммы)

Фронтенд (доработки):

  • /transactions — infinite scroll вместо пагинации (scroll-based)
  • /accounts — отображение баланса в валюте счёта + в базовой валюте
  • Навигация обновлена — добавлены ссылки на Дашборд, Курсы, Налоги, Наличные

Тесты

Запуск всех тестов одной командой (из backend/):

cd backend && uv run pytest

Verbose: cd backend && uv run pytest -v

Фикстуры

Общий conftest.py в tests/ предоставляет:

  • engine — Postgres test DB (budget_app_test) с create_all/drop_all на каждый тест + seed валют
  • client — ASGI клиент с зарегистрированным тестовым юзером
  • auth_headers — JWT Bearer token

Все тесты используют Postgres (не sqlite). Настройка через settings.test_database_url.

Покрытие

37 тестов + 1 skipped:

Файл Тестов Что проверяет
test_health.py 1 Health endpoint
test_models.py 3 Импорт моделей, метаданные, create_all в sqlite
test_auth.py 2 Auth flow (register→login→me), unauthorized
test_api.py 6 Транзакции (list, filter, search), accounts, reports, unauthorized
test_crud.py 6 CRUD транзакций
test_categories.py 8 CRUD групп и категорий
test_accounts.py 6 CRUD счетов, удаление с транзакциями
test_account_balances.py 6 Баланс: доход+расход, переводы, cross_rate, initial_balance

Вычисление баланса

balance = initial_balance + incoming - outgoing

  • incoming = SUM(amount) if cross_rate IS NULL, SUM(amount * cross_rate) если перевод между валютами (для dest_account)
  • outgoing = SUM(amount) — всегда в валюте источника