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

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Включить почтовый дайджест новых сделок по умолчанию — флип DB-дефолта `new_lead.email` на `true` + дотяжка существующих пользователей.

**Architecture:** Backend-only. Миграция (`ALTER COLUMN ... SET DEFAULT` + `UPDATE ... jsonb_set`) через `pgsql_supplier` (на prod BYPASSRLS-роль). Канон схемы + CHANGELOG + фабрика приводятся в соответствие. Движок дайджеста (G2-A) уже читает ключ — кода приложения не добавляем.

**Tech Stack:** Laravel 13 миграции, PostgreSQL 16 (jsonb), Pest 4.

**Spec:** [docs/superpowers/specs/2026-06-19-g2b-new-lead-email-default-on-design.md](../specs/2026-06-19-g2b-new-lead-email-default-on-design.md)

---

## File Structure

| Файл | Ответственность |
|---|---|
| `app/tests/Feature/Notifications/NewLeadEmailDefaultOnTest.php` | **новый** — DB-дефолт через самозапись + backfill SQL |
| `app/database/migrations/2026_06_19_120000_default_new_lead_email_on.php` | **новый** — ALTER DEFAULT + дотяжка |
| `db/schema.sql` | **правка** — дефолт `new_lead.email` true (канон) |
| `db/CHANGELOG_schema.md` | **правка** — +запись |
| `app/database/factories/UserFactory.php` | **правка** — `new_lead.email` true (зеркалит дефолт) |

---

## Task 1: Тест (RED) — дефолт-on + backfill

**Files:**
- Create: `app/tests/Feature/Notifications/NewLeadEmailDefaultOnTest.php`

- [ ] **Step 1: Написать тест**

```php
<?php

declare(strict_types=1);

use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Tests\Concerns\SharesSupplierPdo;

// Самозапись и backfill пишут users через BYPASSRLS pgsql_supplier (нет tenant-GUC
// на публичном роуте). SharesSupplierPdo шарит PDO под DatabaseTransactions.
uses(DatabaseTransactions::class, SharesSupplierPdo::class);

beforeEach(function () {
    config(['services.captcha.fake_passes' => true]);
});

test('самозапись даёт new_lead.email=true по DB-дефолту', function () {
    $this->postJson('/api/auth/register', [
        'email' => 'digeston@example.ru',
        'password' => 'fresh-pass-123',
        'accept_offer' => true,
        'accept_pdn' => true,
        'captcha_token' => 'tok-123',
    ])->assertStatus(201);

    $user = User::where('email', 'digeston@example.ru')->first();
    expect(data_get($user->notification_preferences, 'new_lead.email'))->toBeTrue();
});

test('backfill дотягивает new_lead.email false→true, не трогая соседние', function () {
    $user = User::factory()->create([
        'notification_preferences' => [
            'new_lead' => ['inapp' => true, 'push' => true, 'email' => false],
            'reminder' => ['inapp' => true, 'push' => true, 'email' => true],
            'low_balance' => ['email' => true],
        ],
    ]);

    DB::connection('pgsql_supplier')->statement(<<<SQL
        UPDATE users
           SET notification_preferences = jsonb_set(notification_preferences, '{new_lead,email}', 'true'::jsonb)
         WHERE id = {$user->id}
           AND notification_preferences #> '{new_lead,email}' = 'false'::jsonb
        SQL);

    $fresh = User::find($user->id);
    expect(data_get($fresh->notification_preferences, 'new_lead.email'))->toBeTrue();
    expect(data_get($fresh->notification_preferences, 'reminder.email'))->toBeTrue();
});
```

- [ ] **Step 2: Прогнать — первый тест RED, второй GREEN**

Run: `composer --working-dir=app test -- tests/Feature/Notifications/NewLeadEmailDefaultOnTest.php`
Expected: тест «самозапись даёт …true» — FAIL (текущий DB-дефолт `false`, самозаписанный юзер получает `false`). Тест «backfill …» — PASS (сам выполняет UPDATE). Это подтверждает, что дефолт-тест действительно драйвит изменение.

- [ ] **Step 3: Commit (тест)**

```bash
git add app/tests/Feature/Notifications/NewLeadEmailDefaultOnTest.php
LEFTHOOK=0 git commit -m "test(G2-B): дефолт new_lead.email=true (RED) + backfill SQL

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

## Task 2: Канон схемы + CHANGELOG

**Files:**
- Modify: `db/schema.sql:788`
- Modify: `db/CHANGELOG_schema.md`

- [ ] **Step 1: Флип дефолта в schema.sql**

В [db/schema.sql:788](../../db/schema.sql) заменить строку
```
      "new_lead":         {"inapp": true,  "push": true,  "email": false},
```
на
```
      "new_lead":         {"inapp": true,  "push": true,  "email": true},
```
(меняется одна булева; остальные строки блока — без изменений.)

- [ ] **Step 2: Запись в CHANGELOG_schema.md**

В начало списка изменений [db/CHANGELOG_schema.md](../../db/CHANGELOG_schema.md) добавить запись (по образцу существующих записей файла — дата, краткое описание):

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

- `users.notification_preferences` DEFAULT: `new_lead.email` `false → true`.
  Новые пользователи получают почтовый дайджест новых сделок по умолчанию;
  существующие дотягиваются миграцией (UPDATE false→true, только живые).
  Структурно схема не меняется (правка дефолтного значения), метрики
  (таблицы/индексы/RLS/функции/триггеры) — без изменений.
```

- [ ] **Step 3: Commit (канон)**

```bash
git add db/schema.sql db/CHANGELOG_schema.md
LEFTHOOK=0 git commit -m "chore(G2-B): канон схемы — дефолт new_lead.email true + CHANGELOG

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

## Task 3: Миграция (ALTER DEFAULT + backfill)

**Files:**
- Create: `app/database/migrations/2026_06_19_120000_default_new_lead_email_on.php`

- [ ] **Step 1: Написать миграцию**

```php
<?php

declare(strict_types=1);

use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;

return new class extends Migration
{
    public function up(): void
    {
        // DDL/data через pgsql_supplier (как add_code_fields_to_email_verifications):
        // на dev = postgres superuser, на prod = crm_supplier_worker (BYPASSRLS) —
        // UPDATE users под RLS проходит без tenant-GUC.
        $supplier = DB::connection('pgsql_supplier');

        $default = <<<'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}
}
JSON;

        $supplier->statement(
            "ALTER TABLE users ALTER COLUMN notification_preferences SET DEFAULT '".$default."'::jsonb"
        );

        // Дотяжка существующих: точечно (jsonb_set), только живые, только текущие false.
        $supplier->statement(<<<'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
            SQL);
    }

    public function down(): void
    {
        // Возвращаем только DEFAULT. Дотяжку назад НЕ откатываем: после флипа
        // «исходный false» и выставленный нами «true» неразличимы — откат был бы догадкой.
        $supplier = DB::connection('pgsql_supplier');

        $old = <<<'JSON'
{
  "new_lead":         {"inapp": true,  "push": true,  "email": false},
  "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}
}
JSON;

        $supplier->statement(
            "ALTER TABLE users ALTER COLUMN notification_preferences SET DEFAULT '".$old."'::jsonb"
        );
    }
};
```

- [ ] **Step 2: Прогнать миграцию на тест-БД**

Run: `DB_DATABASE=liderra_testing php app/artisan migrate --force`
Expected: миграция `2026_06_19_120000_default_new_lead_email_on` применена (Ran).

- [ ] **Step 3: Commit (миграция)**

```bash
git add app/database/migrations/2026_06_19_120000_default_new_lead_email_on.php
LEFTHOOK=0 git commit -m "feat(G2-B): миграция — дефолт new_lead.email true + дотяжка существующих

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

## Task 4: Фабрика — зеркалит новый дефолт

**Files:**
- Modify: `app/database/factories/UserFactory.php:39`

- [ ] **Step 1: Флип new_lead.email в фабрике**

В `app/database/factories/UserFactory.php` в массиве `notification_preferences` заменить строку
```php
                'new_lead' => ['inapp' => true, 'push' => true, 'email' => false],
```
на
```php
                'new_lead' => ['inapp' => true, 'push' => true, 'email' => true],
```

- [ ] **Step 2: Прогнать новый тест — обе GREEN**

Run: `composer --working-dir=app test -- tests/Feature/Notifications/NewLeadEmailDefaultOnTest.php`
Expected: оба теста PASS (после миграции Task 3 самозапись даёт `true`).

- [ ] **Step 3: Commit (фабрика)**

```bash
git add app/database/factories/UserFactory.php
LEFTHOOK=0 git commit -m "test(G2-B): UserFactory зеркалит новый дефолт new_lead.email true

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

## Task 5: Регрессия + миграция dev-БД

**Files:** (нет правок — только прогоны)

- [ ] **Step 1: Регрессия затронутых нотификаций**

Run: `composer --working-dir=app test -- tests/Feature/Notifications/NewLeadsDigestJobTest.php tests/Feature/Notifications/NewLeadEmailDefaultOnTest.php`
Expected: все GREEN (движок G2-A не сломан флипом фабрики/дефолта).

- [ ] **Step 2: Применить миграцию на dev-БД**

Run: `php app/artisan migrate --force`
Expected: миграция применена к `liderra` (dev). Самозаписанные клиенты на dev теперь получают `new_lead.email=true` по умолчанию.

---

## Self-Review (выполнено автором плана)

**1. Spec coverage:**
- §3.1 канон схемы + CHANGELOG → Task 2 ✓
- §3.2 миграция (ALTER DEFAULT + backfill, down без отката дотяжки) → Task 3 ✓
- §3.3 фабрика → Task 4 ✓
- §4 тесты (дефолт через самозапись + backfill SQL, регрессия NewLeadsDigestJobTest) → Task 1 + Task 5 ✓
- §5 проверка/выкладка (тест-БД + dev-БД) → Task 3 Step 2 + Task 5 Step 2 ✓
- §6 YAGNI (push/движок/фронт не трогаем) → соблюдено ✓

**2. Placeholder scan:** плейсхолдеров нет; весь код/SQL приведён целиком. JSON-дефолты в up/down совпадают со schema.sql (up — email:true, down — email:false).

**3. Type consistency:** `notification_preferences` (array-cast в User) читается через `data_get(..., 'new_lead.email')` единообразно; путь jsonb `'{new_lead,email}'` одинаков в миграции и тесте; имя миграции `2026_06_19_120000_default_new_lead_email_on` совпадает в Task 3 и Task 3 Step 2 expected.
