43 KiB
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 | Курс на дату операции. |
| U–AB | — | — | Резерв / служебные. |
Семантика двойной записи: одна строка = одно перемещение средств. Если C (дебет) пуст — это вход денег извне (доход). Если D (кредит) пуст — это выход вовне (расход). Если оба заполнены — внутренний перевод (между своими счетами, возможно с конверсией валют через G).
Категории (примеры родительских групп)
💰 Salary, 💸 Комиссии, 📟 Квартплата/Связь, 🧸 Дети, 🏢 Аренда, ⛽️ Транспорт/АЗС, 💊 Медицина, 🛒 Grocery, 🍣 Рестораны, 🧱 Стройка, 💷 Кредиты, 🏖️ Отдых, 💸 Налоги, 🛠️ Быт/Техника, 🚙 Авто, 👚 Одежда, 💰 Дивиденды, 💰 Депозиты.
Подкатегории — конкретные траты (Кафе и рестораны, АЗС, Кровля материалы, Salary 88 05.11, ...).
Счета (примеры)
Demir KGS (Сом), Нал USD (Доллар), Альфа (Рубль), Нал RUB (Рубль), Альфа Кредитка, Сбер, Demir ИП USD, Нал EUR (Евро), и т.д.
Валюты в обращении: RUB, USD, EUR, KGS, KZT, UZS, AED, CNY.
Что таблица умеет уже сейчас
- Учёт транзакций в любой валюте, с автопересчётом в выбранную валюту отчёта.
- Остатки по счетам в реальном времени (через
остаток деб/остаток кред). - Категоризация в иерархии (родитель/подкатегория) с эмодзи.
- Годовые отчёты: план vs факт, доход/расход/инвестиции, % отложено, накопленный капитал, долг.
- Курсы валют с историей (за каждую дату — свой курс).
- Налоговый учёт (отдельный лист
налоги 22-24). - Сводная — динамика по годам.
Болевые точки таблицы
- Excel на 34k+ строк уже тормозит на пересчёте формул (
VLOOKUPна массивы,IF-цепочки в каждой строке). - Ввод транзакций — ручной, через скрытую форму «Наличные», нет автоимпорта банковских выписок.
- Категоризация — ручная.
- Невозможно нормально работать с мобильного.
- Анализ ограничен сводными в Excel: нет нормальных графиков по подкатегориям, кросс-фильтрации, прогнозов.
- Нет ничего про Freedom Gap / инвестиции: нет трекинга портфеля, нормы сбережений, проекции "когда аренда + проекты покроют расходы".
- История курсов хранится в строках листа
курсы— это не нормализованная таблица. - Нет API: нельзя интегрировать с банковскими экспортами, нельзя автоматически тянуть в дашборд.
Концепция веб-приложения
Цель
Заменить Excel приложением, которое:
- Принимает все те же транзакции (multi-currency, multi-account, double-entry).
- Считает аналитику и сводки на лету через SQL, а не через
VLOOKUP. - Импортирует банковские выписки (Альфа, Сбер, Demir, …) автоматически + AI-резолвер для неоднозначных случаев.
- Даёт мобильный UI для быстрого ввода трат на ходу.
- Freedom Gap — разница между расходами и пассивным/полупассивным доходом (аренда, дивиденды, проекты). График прогресса к нулевому gap. What-if сценарии: "если проект даёт X, аренда Y — через N лет свобода".
- Прогнозирует траты на 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.kgXML 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.
Этапы
- Phase 0 — Discovery & schema. Полностью разобрать Excel, утвердить доменную модель. (в процессе — этот док)
- 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). - Phase 2 — Полноценный Viewer + CRUD. Довести до уровня Excel по функциональности:
- Валюта у счетов (символ)
- Категории с иерархией (группа → подкатегория)
- Свёртка по годам/месяцам (drill-down как в Excel)
- Dynamic scrolling (infinite scroll вместо кнопок пагинации)
- CRUD транзакций: добавление, редактирование, удаление
- CRUD категорий
- CRUD счетов
- Ручная правка курсов
- Дашборды: расход по категориям, динамика, бёрндаун по бюджету
- Прогнозы (сезонная модель по категориям)
- Multi-currency: отображение балансов в валюте счёта + в base currency
- Сводная таблица по годам (как лист
своднаяв Excel) - Налоговый учёт (как лист
налоги 22-24в Excel)
- Phase 3 — Импорт банков + AI-резолвер. Автофетч/парсеры (Альфа, Сбер, Demir). LLM-резолвер. TG-бот для ручных подтверждений.
- Phase 4 — Mobile / PWA. Быстрый ввод с телефона.
- Phase 5 — MCP HTTP. Экспонировать MCP-эндпоинт для Hermes.
- 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.tunnel—cloudflare/cloudflared, exposesbudget.qentra.top→frontend: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 (порядок исполнения)
- Init репо:
~/Developer/budget-app/+ git init,.gitignore(python/node/docker/.env), README со ссылкой на этот док в vault. - Backend skeleton:
uv init, FastAPI app, конфиг через pydantic-settings, healthcheck/api/health. - 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.conflisten_addresses+pg_hba.confесли нужно). - Alembic init + initial миграция: SQLAlchemy модели → таблицы.
- FastAPI Users: подключить, роуты
/api/auth/jwt/login,/api/auth/register(после bootstrap закрываем),/api/users/me. - Bootstrap первого юзера:
scripts/create-admin.shсоздаёт Alex из.env. - Импортёр xlsx (
scripts/import-xlsx.py):- Читает
~/Downloads/Budget.xlsxчерезopenpyxl(fallback наlxmlесли timeout'ы). - Парсит листы:
курсы→exchange_rate,категории→category_group/category,счета→account,транзакции→transaction. - Все записи привязываются к
user_idAlex. - Идемпотентно: повторный запуск с тем же файлом не дублирует (hash по
date+amount+source+dest+comment).
- Читает
- Read-only API:
GET /api/transactions?from=&to=&category=&account=&page=— пагинация.GET /api/accounts— счета с балансами (в валюте счёта + в base currency).GET /api/reports/yearly?year=— годовая сводка (план/факт по группам).
- Frontend skeleton:
pnpm create vite, Vue 3 + TS + Pinia, layout (header/sidebar/main), Login страница, JWT в localStorage. - Read-only страницы:
- Transactions: таблица с пагинацией, фильтры по дате/категории/счёту, поиск по комментарию.
- Accounts: список с текущими балансами в валюте счёта + в base currency.
- Reports: годовые сводки 2020–2024 по группам категорий, % сбережений.
- Docker Compose: написать в
~/docker/budget-app/, билдить backend/frontend, прокинутьhost.docker.internalдля Postgres. - Cloudflare Tunnel: создать DNS-запись
budget.qentra.top→ tunnel ID, прописать ingress в cloudflared конфиге. - Smoke-тест:
https://budget.qentra.top→ login → видишь все ~34k транзакций.GET /api/accountsвозвращает все счета с балансами, совпадающими с Excel (±погрешность округления).GET /api/reports/yearly?year=2024совпадает с листом2024 отчет.
Зависимости backend
fastapi,uvicorn[standard],pydantic-settingssqlalchemy[asyncio],asyncpg,alembicfastapi-users[sqlalchemy]openpyxl(xlsx-импорт)httpx(заготовка для cron курсов в Phase 2)python-multipart,passlib[bcrypt]- dev:
pytest,pytest-asyncio,ruff
Зависимости frontend
vue,vue-router,piniaaxios(JWT interceptor)@vueuse/coreecharts(скаффолд под Phase 5, не используем активно)- dev:
vite,typescript,eslint,prettier
Правила работы
- Тесты — обязательны для каждого нового API-роута или изменения. Если код не покрыт тестом — он не готов.
- Обновление доку — после каждой завершённой задачи обновлять таблицу прогресса и Acceptance criteria в этом доке.
- Комит — после каждой логически завершённой задачи (не раз в 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— таблица налогов с фильтрами и сводкой по типам/cash— CashInput.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) — всегда в валюте источника