# 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**. ## Что таблица умеет уже сейчас 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`. - `tunnel` — `cloudflare/cloudflared`, exposes `budget.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 (порядок исполнения) 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: создать БД и юзера в существующем кластере — ```sql 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 шаги (на будущее) ```bash # 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'`. Исправлено вручную: ```sql 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/`): ```bash 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) — всегда в валюте источника