feat(tradein/payments): оплаченный отчёт хранится год — retain_until и предохранители в задаче удаления #2754
No reviewers
Labels
No labels
Fable 5 ревью
GG-форсайт
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
feedback/max
generative
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
ИРД
вторичка
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#2754
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/tradein-paid-retention"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
PR-D1 платёжного контура. Платёжного кода здесь нет ни строчки — это разрядка мины, которая заряжена уже сейчас.
Спецификация:
mera-pr-d-spec.md§1 в корне репо.Зачем
Владелец продаёт отчёт физлицу за 150 ₽: у клиента файл бессрочно, мы храним год.
Сегодня оценка живёт 24 часа, а задача удаления сносит строки
WHERE expires_at < NOW() AND created_by IS NULL— то есть ровно популяцию платящих физлиц. Задача засеяна выключенной, но чек-лист включения лежит в докстринге, и кто-то его выполнит. Первый же прогон после запуска продаж удалил бы оплаченное безвозвратно: PDF нигде не хранится, он рендерится на лету.Почему не поднять срок жизни оценки
Рассматривалось и отклонено по трём независимым причинам, каждой достаточно:
expires_at— отображаемое значение «ДЕЙСТВИТЕЛЕН ДО» в PDF и в интерфейсе; подняв его до года, мы напечатали бы на клиентском документе, что оценка квартиры актуальна год;expires_atна −24 часа — сдвиг сломал бы и её.Что сделано
Отдельная колонка
retain_until(миграция 234, без бэкфилла). Два независимых срока:expires_atretain_untilNULLдаётfalseв сравнении, поэтому для всех 1058 существующих строк и всего B2B поведение не меняется вообще.Единый гейт чтения. Их было два, и они уже разошлись по коду ответа: фильтр в SQL давал 404, проверка в Python — 410. Теперь одно определение «оценка читаема» на оба места. Третий потребитель (ссылка-пропуск из будущего PR) разошёлся бы неизбежно.
Текст ответа
"estimate expired (24h TTL)"заменён на"estimate expired"— при годовом хранении упоминание суток становится ложью в ответе API.Задача удаления — два независимых предохранителя плюс предполётная проверка:
retain_until IS NULL— именно так, не< NOW(). Оплаченное не удаляется в принципе; ослабление это отдельный PR не раньше чем через год после первой продажи.NOT EXISTSпо платежам — независимая страховка на случай, если выдача забыла проставить срок из-за бага или гонки. Строка, которой касались деньги, переживёт задачу в любом случае.Удаление лидов не тронуто — там своя механика и настоящий 180-дневный срок.
Число «12 месяцев» больше нигде не хардкодится — одна настройка через переменную окружения, из неё выводятся и текст, и SQL продления.
Страница политики больше не утверждает, что механизма удаления нет — после смерженного PR #2547 это неправда.
Проверка
Миграция прогнана на прод-БД внутри
BEGIN … ROLLBACK, тело дважды — идентично.Неизменность прода проверена мной независимо от отчёта автора: колонки нет, индекса нет, записи в реестре миграций нет, данные целы (1058 строк, из них 129 анонимных).
pytest— 3966 passed, 14 skipped.tscи линтеры чисто.Критерии приёмки — тестами
expires_at, но живымretain_untilчитается и отдаёт PDF;retain_untilне выбирается предикатом удаления; строка с любой записью о платеже не выбирается даже при пустомretain_until;Замечание по процессу
Автор попутно наткнулся на ловушку: форматтер фронтенда настроен только на
frontend/, но не наtradein-mvp/frontend/. Прогон по привычке раздул дифф широким переформатированием несвязанных строк — откатил и применил правки точечно. Стоит иметь в виду при следующих правках фронта МЕРЫ.Заметил при разборе конфликтов, сообщаю до того, как это упрётся в гейт.
Номер миграции 234 занят дважды
main(смержено вчера, #2765)234_scrape_runs_ban_kind_unknown.sql234_trade_in_estimates_retain_until.sqlНа проде применён первый —
SELECT max(filename) FROM _schema_migrationsдаёт234_scrape_runs_ban_kind_unknown.sql. Ветка ответвилась 06.08 отd3d74642, до того как 234 занялся.Прод отслеживает миграции по голому имени файла, поэтому одинаковый номер с разными именами применится дважды и в разном порядке на разных стендах. Ровно от этого стоит гейт
test_migrations_manifest.py; он это поймает, но уже после переименования будет поздно откатывать.Единственный конфликт слияния у PR — тоже про это:
_manifest_applied.txt, обе стороны дописали свои имена. Механически: взять оба блока и отсортировать (гейт требует сортировки и отсутствия дублей, и подскажет, если что-то не так).Какой номер брать
Свободен 240. Диапазон 235–239 сейчас в работе у моих задач — часть, вероятно, останется неиспользованной, но пока их лучше не занимать:
views_totalЕсли возьмёте 240, столкновения не будет ни с чем из перечисленного.
Второй PR в конфликте — #2546
Не связан с этим, но раз уж смотрю:
feat/mera-b2c-antiabuseответвился 26.07, с тех пор вmain241 коммит. Конфликтуют два файла:tradein-mvp/backend/app/api/v1/trade_in.pytradein-mvp/backend/tests/conftest.pyЕщё три сливаются автоматически (
core/config.py,services/estimator.pyи др.). Чем дольше ветка живёт, тем дороже; если этап 2 из 8 ещё актуален, его дешевле перебазировать сейчас.Ветки ваши — я в них ничего не трогал и не буду.