Самокритика v0.7 — закрытие беклога после E1
Дата: 2026-05-15 Перед этим: 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
- Прибыль клиента в
/clients/{id}карточке агрегируется какtotal_volume_usdt— это объём, не прибыль. KPI «прибыль с клиента» отсутствует. Можно добавить через JOIN deals.profit_usdt. Не критично. - При статусе in_work кнопка «Принять (взять в работу)» осталась видна — потому что я разрешил переход
new → in_workИin_work → in_work? Нет,in_work → in_workне в списке. Окей, всё корректно. - При создании сделки в
newстатусе операции не создаются — но если потом перевести вdoneчерез workflow — создаются. Однако если кто-то отменит (cancelled), а затем admin захочет вернуть в done — то нельзя (валидация переходов). Это сейчас норма — отменённые не возрождаются. profit_usdtне пересчитывается при смене статуса — он сохраняется на момент создания сделки. Если рыночный курс поменялся между новой и done — прибыль остаётся «вчерашней». Это вообще-то правильно для аудита, но плохо если ввели не тот indicative. Решение: дать редактирование сделки до момента done.- Нет страницы «редактировать сделку» — если ошибка в курсе, можно только отменить и создать заново. Сделать в E1.2.
- Нет страницы редактирования клиента — только создание. То же — в 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 (бот + карточка).