# G2-B — дайджест новых сделок по умолчанию — дизайн

**Дата:** 19.06.2026
**Статус:** дизайн утверждён владельцем (брейншторм)
**Находка:** G2 (go-live), часть B. G2-A (движок дайджеста) сделан `f9438714`.
**Граница:** backend/данные. Движок, письмо, шаблон, фронт-галочка — не трогаем.

---

## 1. Цель

Включить почтовый дайджест новых сделок **по умолчанию**: новые клиенты получают
его автоматически, существующие — дотягиваются. Отключение остаётся за клиентом
(галочка «Новый лид / Email» в настройках, тот же ключ `new_lead.email`).

Решение владельца: флип дефолта `new_lead.email` `false → true` + дотяжка
**всех живых** пользователей `false → true`.

---

## 2. Контекст (как сейчас)

- Движок G2-A (`SendNewLeadsDigestJob` → `NotificationService::notifyNewLeadsDigest`)
  шлёт дайджест пользователям с `notification_preferences.new_lead.email = true`.
- Дефолт `notification_preferences` в [db/schema.sql:787-796](db/schema.sql#L787)
  имеет `"new_lead": {..., "email": false}` → по умолчанию дайджест выключен.
- `UserFactory` (`app/database/factories/UserFactory.php:38-47`) **явно** дублирует
  тот же дефолт (с `new_lead.email => false`) — по своему комментарию «зеркалит
  schema-default».
- `RegistrationService::createPendingTenantOwner` создаёт `User` **без**
  `notification_preferences` → полагается на DB DEFAULT. То есть после флипа
  самозаписанные клиенты получают `new_lead.email = true` из дефолта БД.

---

## 3. Что меняем

### 3.1. Канон схемы

[db/schema.sql:788](db/schema.sql#L788): в дефолте `notification_preferences`
ключ `new_lead.email` `false → true`. Остальные ключи (`inapp`, `push` и прочие
события) — без изменений. Запись в [db/CHANGELOG_schema.md](db/CHANGELOG_schema.md)
(правило §4.2: правка schema.sql только с CHANGELOG).

Новый блок дефолта (меняется одна булева):

```json
{
  "new_lead":         {"inapp": true,  "push": true,  "email": true},
  "reminder":         {"inapp": true,  "push": true,  "email": true},
  "low_balance":      {"email": true},
  "zero_balance":     {"email": true},
  "topup_success":    {"email": true},
  "invoice_paid":     {"email": true},
  "new_device_login": {"email": true},
  "marketing":        {"email": false}
}
```

### 3.2. Миграция

`app/database/migrations/<ts>_default_new_lead_email_on.php`:

- **up:**
  1. `ALTER TABLE users ALTER COLUMN notification_preferences SET DEFAULT '<JSON выше>'::jsonb` — новые пользователи получают `email:true`.
  2. Дотяжка существующих (точечно, сохраняя остальные настройки, только живые,
     только текущие `false`):
     ```sql
     UPDATE users
        SET notification_preferences = jsonb_set(notification_preferences, '{new_lead,email}', 'true'::jsonb)
      WHERE deleted_at IS NULL
        AND notification_preferences #> '{new_lead,email}' = 'false'::jsonb;
     ```
- **down:** вернуть DEFAULT на `email:false` (полный прежний JSON-блок).
  Дотяжку **назад не откатываем** — после флипа «исходный false» и «выставленный
  нами true» неразличимы; откат значений был бы догадкой. Зафиксировать
  комментарием в `down()`.

Соединение/роль: как у обычных миграций проекта, изменяющих существующие таблицы
(см. образец — ближайшая миграция-ALTER на `users`/таблицу с RLS). На dev —
`postgres` (superuser, RLS bypass), на prod — `crm_migrator` (владелец таблиц,
RLS bypass). Конкретный идиом подтверждается при написании плана по существующему
образцу.

### 3.3. Фабрика

`app/database/factories/UserFactory.php:39`: `new_lead.email` `false → true`,
чтобы фабрика и дальше зеркалила schema-default (её собственный контракт). Иначе
фабричные пользователи в тестах разошлись бы с реальным дефолтом.

---

## 4. Тесты (Pest)

`app/tests/Feature/Notifications/NewLeadEmailDefaultOnTest.php`
(`uses(DatabaseTransactions::class, SharesSupplierPdo::class)` — пишем users через
pgsql_supplier, как RegistrationTest):

1. **DB DEFAULT через реальный путь:** самозапись (`POST /api/auth/register`) → у
   созданного пользователя `notification_preferences.new_lead.email === true`
   (полагается на DB DEFAULT, т.к. `RegistrationService` prefs не задаёт).
2. **Дотяжка (SQL миграции):** вставить пользователя с `new_lead.email=false`,
   выполнить тот же backfill-`UPDATE` (jsonb_set) → значение стало `true`, а
   соседний ключ (напр. `reminder.email`) не изменился.

Регрессия: `NewLeadsDigestJobTest` (G2-A) остаётся зелёным.

Все — GREEN на `composer --working-dir=app test`.

---

## 5. Проверка / выкладка

- `composer --working-dir=app test -- tests/Feature/Notifications/NewLeadEmailDefaultOnTest.php` и `...NewLeadsDigestJobTest.php` — зелёные.
- Миграция прогоняется на dev-БД (`liderra`) и тест-БД (`liderra_testing`).
- vitest не нужен (backend).

---

## 6. YAGNI / границы

- Не трогаем `push` в дефолте (это G4, отдельная находка).
- Не трогаем движок/письмо/шаблон/планировщик (G2-A готов).
- Не трогаем фронт-галочку (управляет тем же ключом, поведение не меняется).
- Откат дотяжки в `down()` не делаем (неразличимость значений).

---

## 7. Затрагиваемые файлы

| Файл | Действие |
|---|---|
| `db/schema.sql` | правка дефолта `new_lead.email` true |
| `db/CHANGELOG_schema.md` | +запись |
| `app/database/migrations/<ts>_default_new_lead_email_on.php` | новый |
| `app/database/factories/UserFactory.php` | `new_lead.email` true |
| `app/tests/Feature/Notifications/NewLeadEmailDefaultOnTest.php` | новый |
