Commit Graph

2 Commits

Author SHA1 Message Date
Дмитрий 001d7819bf fix(supplier): close code-review BLOCKER+WARN — FK + CHECK + resolver guard
Code-review subagent (CV.12 в Plan 1) нашёл 1 BLOCKER + 2 actionable WARNINGs:

1. **BLOCKER** — projects.supplier_b{1,2,3}_project_id были голыми BIGINT без
   REFERENCES, вопреки явному комментарию «FK добавятся в Task 2». Task 2
   создал supplier_projects, но FK на projects не вернул. Можно было записать
   произвольный BIGINT в эти колонки.
   Fix: ALTER TABLE projects ADD CONSTRAINT … FOREIGN KEY … ON DELETE SET NULL
   для всех трёх + 3 partial index (WHERE NOT NULL) для FK lookup.

2. **WARNING** (Project-level B1+SMS guard) — CHECK существовал только на
   supplier_projects; Project::create(['signal_type'=>'sms','supplier_b1_project_id'=>…])
   проходил вопреки spec §2.2 «B1 не поддерживает СМС».
   Fix: ADD CONSTRAINT chk_projects_b1_not_for_sms
   CHECK (signal_type <> 'sms' OR supplier_b1_project_id IS NULL).

3. **WARNING** (resolver collision) — SupplierProjectResolver::resolveOrStub
   firstOrCreate на (platform, unique_key) без signal_type → при коллизии
   unique_key возвращал чужую запись с другим signal_type без ошибки.
   Fix: после firstOrCreate проверяется match signal_type, иначе DomainException.
   +1 тест на collision.

Schema bumped v8.16 → v8.17. Метрики: 60 таблиц / 111 индексов (+3) / 39 RLS.
Pest: 500/498 passed (+1 collision test). Larastan 0 errors. Pint clean.

Spec: §2.1, §2.2
Plan: Task 2 (закрытие code-review CV.12)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 17:32:02 +03:00
Дмитрий 59c57f9ec0 feat(services): add SupplierProjectResolver (resolveOrStub with B1+SMS guard)
- app/Services/SupplierProjects/SupplierProjectResolver.php — резолвер
  по ключу (platform, signal_type, unique_key). Возвращает existing supplier_project
  или создаёт pending stub (физическая sync произойдёт в SyncSupplierProjectsJob, Plan 3).
- Защита 1: InvalidArgumentException на платформу не из {B1,B2,B3}.
- Защита 2: InvalidArgumentException на signal_type не из {site,call,sms}.
- Защита 3: DomainException на B1+SMS combo (chk_supplier_projects_b1_not_for_sms).
- tests/Feature/Services/SupplierProjectResolverTest.php — 6 тестов:
  resolve existing, create stub, idempotency (no duplicates), B1+SMS guard,
  invalid platform, invalid signal_type.

Pest: 475 / 473 passed / 2 skipped (467 + 6 новых = 473).
Larastan: 0 errors. Pint passed.

Spec: §2.2, §4.1
Plan: Task 11

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 17:02:25 +03:00