# G3 — Полное удаление фичи «Напоминания» из портала Лидерры

**Дата:** 2026-06-19
**Тип:** удаление фичи (go-live находка G3)
**Глубина (решение владельца):** полная вычистка, **включая таблицу `reminders` в БД**.
**Источник решения:** `docs/superpowers/specs/2026-06-18-gfindings-session-handoff.md` (таблица G1–G7, строка G3 «убрать Напоминания совсем»).

---

## 1. Цель

Убрать фичу «Напоминания по сделкам» из продукта целиком — экран, серверный код,
рассылку, крон и таблицу в БД — так, чтобы не осталось «хвостов» (правило CLAUDE.md §5 п.11)
и при этом **ничего смежного не сломалось** (опрос баланса, дайджест новых сделок,
общий механизм уведомлений, список сделок).

**Терминология:** фича «Напоминания» = пользовательские напоминания-касания по сделке
(таблица `reminders`, контроллер `ReminderController`, экран `/reminders`, раздел в карточке
сделки). **Не путать** с `BalanceFrozenReminder*` (биллинговое доначисление по заморозке) —
**это другое, его НЕ трогаем.**

---

## 2. Что удаляется (полный инвентарь)

### 2.1. Фронт — видимое клиенту

| Артефакт | Действие |
|---|---|
| Роут `/reminders` в `resources/js/router/index.ts` (devIndex 11) | удалить запись роута |
| `resources/js/views/RemindersView.vue` | удалить файл |
| `resources/js/components/reminders/{RemindersList,RemindersFilters,ReminderDialog}.vue` | удалить каталог |
| `resources/js/stores/reminders.ts` | удалить файл |
| `resources/js/api/reminders.ts` | удалить файл |
| `resources/js/histoire.setup.ts` — стаб-роут `/reminders` | удалить строку |
| Раздел «Напоминания» в `resources/js/components/deals/DealDetailBody.vue` (секция `data-testid="reminders-section"`, импорт `remindersApi`/`ApiReminder`/`ReminderDialog`, ref'ы, `loadReminders`/`completeReminderInDrawer`/`onReminderSaved`/`formatReminderTime`, CSS `.reminders-list/.reminder-*`) | вырезать блок целиком |
| `resources/js/layouts/AppLayout.vue` — `useRemindersStore`, `loadReminderCounts`, `usePolling(loadReminderCounts…)` | удалить (⚠ ШОВ — см. §3) |
| `resources/js/components/layout/AppTopbar.vue` — ключ `reminder` в icon-map уведомлений | удалить ключ |
| `next_reminder_at` во фронте: `api/deals.ts` (тип), `composables/dealsApiMapper.ts` (`nextReminderAt`), `composables/mockDeals.ts`, `DealsTable.vue` | убрать поле/маппинг |
| Преференция «Напоминание»: `views/settings/NotificationsTab.vue` (запись `EVENTS`), `api/notifications.ts` (`'reminder'` в union) | удалить |

### 2.2. Backend

| Артефакт | Действие |
|---|---|
| `app/Http/Controllers/Api/ReminderController.php` | удалить файл |
| `routes/web.php` — группа `/api/reminders` (5 роутов) + `Route::view('/reminders','welcome')` | удалить |
| `app/Models/Reminder.php` | удалить файл |
| `database/factories/ReminderFactory.php` | удалить файл |
| `app/Console/Commands/RemindersDispatchDue.php` | удалить файл |
| `app/Mail/ReminderDueNotification.php` + `resources/views/emails/reminder.blade.php` | удалить |
| `app/Services/NotificationService.php` — `EVENT_REMINDER`, `notifyReminder()`, импорты `Reminder`/`ReminderDueNotification`, упоминание `reminder` в doc-комментарии дефолта | вырезать |
| `app/Http/Controllers/Api/DealController.php` — `addSelect(['next_reminder_at' => …reminders…])` (стр.~106–112) + маппинг `next_reminder_at` в ресурсе (стр.~221–222) | вырезать (⚠ ШОВ — см. §3) |
| Дефолт `notification_preferences` без ключа `reminder`: `database/factories/UserFactory.php`, `database/seeders/DemoSeeder.php` | убрать ключ |

### 2.3. БД (схема — источник истины + миграция)

| Артефакт | Действие |
|---|---|
| Миграция `database/migrations/<ts>_drop_reminders_table.php` | `Schema::dropIfExists('reminders')` (RLS-политика + 4 индекса уходят каскадом с таблицей); `down()` — воссоздание таблицы по текущему DDL (для отката dev) |
| `db/schema.sql` — `CREATE TABLE reminders` (стр.~1907) + 4 индекса (`idx_reminders_*`) + `ALTER TABLE reminders ENABLE ROW LEVEL SECURITY` + `CREATE POLICY tenant_isolation ON reminders` + `COMMENT ON …reminders…` | удалить блоки |
| `db/schema.sql` — дефолт `notification_preferences` (JSONB, ~стр.699) без ключа `reminder` | убрать ключ |
| `db/schema.sql` header (метрики + версия) + `db/CHANGELOG_schema.md` | бамп версии (минор), запись об удалении таблицы; ревью агентом `rls-reviewer` |

### 2.4. Тесты и мусор

| Артефакт | Действие |
|---|---|
| `tests/Feature/ReminderControllerTest.php`, `tests/Feature/RemindersDispatchDueTest.php` | удалить |
| `tests/Frontend/{RemindersView,reminders-store,reminders-api,ReminderDialog}.spec.ts` | удалить |
| Поправить (убрать `next_reminder_at`): `tests/Feature/DealIndexTest.php`, `tests/Frontend/{dealsApiMapper,DealsView,DealDetailDrawerApi,DealsListIntegration}.spec.ts` | edit |
| Поправить (убрать `reminder`-преференцию): `tests/Feature/Auth/NotificationPreferencesTest.php`, `tests/Frontend/NotificationsTab.spec.ts` | edit |
| `tests/Feature/Notifications/InAppNotificationTest.php` — пример события `'reminder'` → заменить на живое (`'new_lead'`) (⚠ ШОВ — см. §3) | edit |
| `app/phpstan-baseline.neon` — строки про `ReminderController.php`, `ReminderFactory.php`, `ReminderControllerTest.php`, `RemindersDispatchDueTest.php`, `Deal::$next_reminder_at` | удалить совпадения |
| `app/dev-indices.json` — запись devIndex 11 «Напоминания» | удалить, если есть |

---

## 3. Швы (точки сцепления — главный риск)

1. **`POLLING_REMINDERS_INTERVAL_MS`** (`constants/polling.ts`) используется и опросом напоминаний,
   **и опросом баланса** (`AppLayout.vue`, `usePolling(loadBalanceStatus, …)`).
   → **Константу НЕ удалять** (баланс на ней висит). Удаляем только вызов опроса напоминаний
   + импорт стора + `loadReminderCounts`. Комментарий у константы при желании поправить
   («менее срочные счётчики»), но это не обязательно.

2. **`next_reminder_at`** — это **не колонка** `deals`, а вычисляемый подзапрос к `reminders`
   в `DealController::index`. После DROP таблицы подзапрос упал бы с ошибкой → удаляем подзапрос,
   поле в ресурсе, тип `deals.ts`, маппинг и все тест-ассерты. Колонок-напоминаний в `deals` нет
   (удалены ещё в schema v8.3) — `deals` саму не трогаем.

3. **`InAppNotificationTest`** дергает `notifyInApp($user, 'reminder', …)` — тест проверяет
   **общий** механизм in-app уведомлений, `'reminder'` тут лишь как пример строки события.
   → тест **не удалять**, заменить пример события на живое (`'new_lead'`), иначе он будет
   ссылаться на удалённое событие.

4. **Дефолт `notification_preferences`** (JSONB) трогается одновременно в 3 местах
   (`schema.sql`, `UserFactory`, `DemoSeeder`) — держать синхронно, **не задев** соседние ключи
   `new_lead` (его дефолт недавно флипнули в G2-B), `low_balance`, `zero_balance` и т.д.

5. **Общий механизм уведомлений** (`InAppNotification`, `sendEmail`, `notifyInApp`,
   `prefEnabled`) — каркас, **остаётся**. Удаляем только ветку `reminder`.

---

## 4. Сознательные решения (подтверждены владельцем)

1. **Существующие данные на боевом скриптом не трогаем.** Оставшиеся в `users.notification_preferences`
   ключи `reminder` и старые in-app уведомления типа `reminder` — инертный безвредный осадок;
   рисковать jsonb-апдейтом/удалением строк на проде не оправдано. Удаляем только из дефолта/кода/UI.
2. **Внешний крон `reminders:dispatch-due`** (планировщик Windows на боевом; в `routes/console.php`
   его НЕТ — заведён снаружи) после удаления команды начнёт ошибаться → **выключить на сервере
   отдельным операционным шагом деплоя** (действие владельца).
3. **Применение на проде** (миграция-DROP + выключение крона) — **отдельный деплой с разрешения
   владельца**, не в этой сессии. Сейчас — только локалка + тест-БД + push в gitea.

---

## 5. Порядок работ (для плана)

1. **Baseline:** прогнать затрагиваемые тесты ДО правок (зелёные) — чтобы отличить «сломали» от «уже было».
2. **Фронт:** удалить экран/компоненты/стор/api/раздел в карточке/опрос/преференцию/`next_reminder_at`.
3. **Backend:** удалить контроллер/роуты/модель/фабрику/команду/письмо/сервис-ветку/подзапрос/дефолты.
4. **БД:** миграция-DROP + правка `schema.sql` + CHANGELOG + бамп версии; прогнать на dev и тест-БД.
5. **Тесты/baseline:** удалить мёртвые тесты, поправить смежные, вычистить `phpstan-baseline.neon`.
6. **Ревью:** агент `rls-reviewer` по диффу схемы.
7. **Верификация (§6).**
8. **Push** `git push gitea main`.

---

## 6. Верификация

- **Backend (Pest):** прогон затронутых и смежных тестов —
  `DealIndexTest`, `NotificationPreferencesTest`, `InAppNotificationTest`,
  `BalanceNotificationsTest`, `NewLeadsDigestJobTest`, `NewLeadEmailDefaultOnTest`
  + полный прогон модуля Notifications/Deal. Все зелёные. Larastan (`composer stan`) — 0 ошибок,
  baseline чист (нет «unmatched ignored error»).
- **Фронт:** vitest сломан (G8) → `vue-tsc` (`npm --prefix app run type-check`) = 0,
  `eslint` (`npm run lint:vue`) = 0, `npm --prefix app run build` успешен,
  + **живой Playwright**: (а) `/reminders` отдаёт 404/редирект, не белый экран;
  (б) карточка сделки открывается без раздела «Напоминания» и без ошибок в консоли;
  (в) список сделок грузится (нет `next_reminder_at`-поломки);
  (г) Настройки → Уведомления открываются без строки «Напоминание»;
  (д) дашборд/шапка грузятся, опрос баланса жив.
- **Схема:** `rls-reviewer` GREEN; в `schema.sql` нет orphan-ссылок на `reminders`.

**Критерий готовности:** все тесты зелёные, type-check/eslint/build/stan чисты, Playwright-сценарии (а)–(д) проходят, в коде нет ни одной живой ссылки на удалённую фичу (grep `Reminder`/`reminders` даёт только `BalanceFrozenReminder*` и историко-комментарии schema).

---

## 7. Вне объёма

- `BalanceFrozenReminder*` (биллинг) — **не трогаем**.
- Чистка исторических данных на проде (jsonb-ключи, старые уведомления) — не делаем (см. §4.1).
- Тарифы (лид ≤65₽) — отдельная биллинг-работа, вне G-списка.
