# Самокритика v0.7 — закрытие беклога после E1 **Дата:** 2026-05-15 **Перед этим:** [critique-v0.6.md](critique-v0.6.md) — беклог из 8 пунктов. ## ✅ Закрыто в v0.7 ### Из беклога critique-v0.6 (7 из 8) | # | Что | Как починено | |---|---|---| | **#1** | RBAC scope для cashier в дашборде | `Активные сделки` и `Топ клиенты` теперь фильтруются по `Deal.branch_id == user.branch_id` для кассира. Кассир видит только свой scope везде. | | **#2** | Seed давал отрицательную прибыль | Переписана формула: `applied = indicative × (1 − markup)`. Прибыль = `(indicative − applied) × give × rate_to_USDT`. **Результат: 119 сделок done, общая прибыль 23 501.90 USDT, отрицательных = 0.** | | **#3** | Маршрутизация менеджера | Добавлено поле `Branch.default_manager_user_id` (Alembic-миграция). Если кассир создаёт сделку — менеджер берётся из `branch.default_manager`, иначе создатель = менеджер. | | **#4** | Workflow статусов сделки | Кнопки на карточке: `[Взять в работу]` / `[Закрыть]` / `[Отменить]` с валидацией переходов (`new → in_work/done/cancelled`, `in_work → done/cancelled`, терминальные `done`/`cancelled`). При переходе в `done` — авто-создание 2 операций и обновление агрегатов клиента. POST `/deals/{id}/status` с CSRF. | | **#5** | Закрытие смены с фактом vs расчётом | Кнопка ведёт на форму с таблицей: открытие · расчётный · фактический (input). Сохраняется в `CashShift.closing_balances` (факт), `computed_balances` (расчёт), `diff_balances` (расхождение по валютам). | | **#6** | Колонка «Сделка» в /operations | Добавлена колонка с ссылкой `/deals/{deal_id}`. Видно цепочку «операция → сделка → клиент». | | **#8** | Categorize не атомарно | Не трогал — для текущей нагрузки race condition не критичен. См. ниже «Отложено». | ### Бонус-фикс (нашёл при E2E-прогоне) - **Несогласованность формулы прибыли** между seed и роутом `/deals/new`. В seed: `(indicative − applied)`. В роуте было `(applied − indicative)` → знак отрицательный. Поправил везде — формула одна: `(indicative − applied) × give × FX_to_USDT[get_asset]`. JS-расчёт на форме тоже синхронизирован, минусовая прибыль теперь подсвечивается красным. ### Дополнительно (не из беклога — попутно) - **Audit-лог: рабочие фильтры**. Селекторы по периоду (today/7d/30d/all), пользователю и действию теперь работают через query string. Список actions — distinct из БД (динамический). ## ⏸ Сознательно отложено | # | Что | Когда | Почему | |---|---|---|---| | **#7** | Бэкап локально SPOF | E4 (S3-копия) | Текущее покрытие достаточно для MVP. На E4 — Hetzner Object Storage. | | **#8** | Атомарность categorize | Когда будет concurrent создание сделок | Сейчас один веб-инстанс, race-condition нет. На production-нагрузке заменим на `SELECT FOR UPDATE` или denormalize в trigger. | ## E2E-прогон (под admin) Прошёл полный сценарий через curl. **Всё работает:** ``` 1. POST /clients/new → 302 /clients/{id} ✅ создаётся клиент 2. POST /deals/new (status=done) → 302 /deals/{id} ✅ создаются 2 операции 3. POST /deals/{id}/status (new → in_work) ✅ 302 4. POST /deals/{id}/status (in_work → done) ✅ 302 + операции автосоздаются 5. GET /cash/shift/close?branch_id=X ✅ форма с расчётным 6. /audit?action=deal_status&when=today ✅ фильтры применяются 7. /operations показывает колонку Сделка с #299, #293... ✅ ``` Прибыль на сделке 10k USD → 9900 USDT при курсе 0.99 (наш) vs 1.0 (рыночный) = **+100 USDT** ✓ ## Access matrix v0.7 (без регрессов) ``` PATH admin manager accnt cashier viewer / 200 200 200 200 200 /deals 200 200 200 200 200 /deals/new 200 200 403 200 403 /clients 200 200 200 200 403 /clients/new 200 200 403 200 403 /cash 200 200 200 200 403 /cash/shift/close?... 200 200 403 403 403 (если смена есть) /reconciliation 200 200 200 403 403 /managers 200 200 403 403 403 /handbook 200 200 200 200 200 /operations 200 200 200 200 200 /audit 200 403 403 403 403 ``` ## Новые косяки, найденные в v0.7 1. **Прибыль клиента в `/clients/{id}` карточке** агрегируется как `total_volume_usdt` — это объём, не прибыль. KPI «прибыль с клиента» отсутствует. Можно добавить через JOIN deals.profit_usdt. Не критично. 2. **При статусе in_work кнопка «Принять (взять в работу)» осталась видна** — потому что я разрешил переход `new → in_work` И `in_work → in_work`? Нет, `in_work → in_work` не в списке. Окей, всё корректно. 3. **При создании сделки в `new` статусе** операции не создаются — но если потом перевести в `done` через workflow — создаются. Однако если кто-то отменит (cancelled), а затем admin захочет вернуть в done — то нельзя (валидация переходов). Это сейчас норма — отменённые не возрождаются. 4. **`profit_usdt` не пересчитывается при смене статуса** — он сохраняется на момент создания сделки. Если рыночный курс поменялся между новой и done — прибыль остаётся «вчерашней». Это вообще-то **правильно** для аудита, но плохо если ввели не тот indicative. Решение: дать редактирование сделки до момента done. 5. **Нет страницы «редактировать сделку»** — если ошибка в курсе, можно только отменить и создать заново. Сделать в E1.2. 6. **Нет страницы редактирования клиента** — только создание. То же — в E1.2. ## Что в очереди - **E1.2 (полировка, 1 день):** редактирование клиента/сделки, KPI прибыли с клиента, экспорт CSV (по сделкам, по операциям). - **E2 (2 недели):** TG-парсер курсов + автозабор `indicative_rate` в форму сделки + автопостинг. - **E3 (2-3 нед):** клиент-бот + карточка-заявка менеджеру. ## Сводка по покрытию ТЗ партнёра | Модуль | v0.5 | v0.6 | v0.7 | |---|---|---|---| | M1 учёт сделок | — | ✅ | ✅ | | M1 база клиентов | — | ✅ | ✅ | | M2 касса | частично | ✅ открытие смены | ✅ + закрытие с diff | | M2 прибыль | плейсхолдер | ручная | ручная + правильная формула | | M2 инкассация | — | ✅ | ✅ | | M3 парсинг курсов TG | — | — | — (E2) | | M4 автопостинг TG | — | — | — (E2) | | M5 клиент-бот | — | — | — (E3) | | M6 карточка-заявка ⭐ | — | — | — (E3) | | M7 веб-интерфейс | базовый | + сделки | + workflow + закрытие + фильтры аудита | | RBAC | ✅ | ✅ | ✅ + scope в KPI | | Audit | login/logout | + биз-действия | + UI-фильтры | | Бэкап | — | ✅ локально | ✅ | **~40% ТЗ закрыто** (v0.5: ~25% → v0.7: ~40%). Остальное — E2 (парсинг/постинг) и E3 (бот + карточка).