# Приёмка liderra.ru — ПУНКТ №3: Каскад региона (точный → вся РФ → непокрытый, подмена города)

> **Для исполнителя:** формат PR1 (одобрен 20.06). Каждый шаг = `🔧 Код-факт (file:line)` · `🎬 Действие` · `📋 ОТЧЁТ (было→ожидали→стало + 📸/📝)` · `❌ Если не так`. Техника «под капотом»; владельцу — человеческие карточки.

**Цель:** показать, что портал умно определяет **регион заявки** и доставляет её по «лесенке»:
1. сначала тем, у кого этот регион **точно указан**;
2. если таких мало — добирает теми, кто берёт **всю Россию**;
3. если регион вообще никто не ловит — отдаёт по **запасному каналу**, и при этом в сделке всё равно показывается **настоящий город заявки** (не подменённый).

**Что снимаем глазами:** раздел **Сделки** — колонка/карточка «Город» показывает реальный регион заявки. Живые 📸.

**Источник сценария:** свод проверок №3 (D-CASCADE 1/2/3); фича «регион по телефону + каскад» (на проде, флаг ON).

---

## 🔧 Код-факты (подтверждено чтением 20.06)

- **Определение региона:** DaData → реестр Россвязи → тег-фолбэк — [LeadRegionResolver::resolve :40-67](../../../app/app/Services/LeadRegionResolver.php#L40-L67); тег-фолбэк — [:129-142](../../../app/app/Services/LeadRegionResolver.php#L129-L142). **Флаг `dadata.enabled=false` → сразу тег** — [:42-44](../../../app/app/Services/LeadRegionResolver.php#L42-L44).
- **Каскад доставки (3 фазы)** — [LeadRouter::matchEligibleProjects :55-94](../../../app/app/Services/LeadRouter.php#L55-L94):
  - Фаза 1 — точное совпадение региона (`routing_step=1`) — [:60-69](../../../app/app/Services/LeadRouter.php#L60-L69);
  - Фаза 2 — «вся РФ» (`regions='{}'`), добор недостающих слотов (`routing_step=2`) — [:71-82](../../../app/app/Services/LeadRouter.php#L71-L82);
  - Фаза 3 — запасной канал, если регион никто не ловит и нет «вся РФ» (`routing_step=3`) — [:84-93](../../../app/app/Services/LeadRouter.php#L84-L93).
- **Город и подмена в сделке** — [RouteSupplierLeadJob :446-452](../../../app/app/Jobs/RouteSupplierLeadJob.php#L446-L452): `city` = человекочитаемое имя **НАСТОЯЩЕГО** региона лида (`subjectCode → CODE_TO_NAME`) — [:446-450](../../../app/app/Jobs/RouteSupplierLeadJob.php#L446-L450); `region_substituted = (routing_step === 3)` — [:452](../../../app/app/Jobs/RouteSupplierLeadJob.php#L452).
- Резолв сохраняется на лиде: `resolved_subject_code, region_source, dadata_qc, phone_operator` — [:160-163](../../../app/app/Jobs/RouteSupplierLeadJob.php#L160-L163); маршрутизация по резолву — [:167](../../../app/app/Jobs/RouteSupplierLeadJob.php#L167).
- Коды регионов (порядковые 1..89): Москва=82, СПб=83, Татарстан=16, Москобл=56 — `App\Support\RussianRegions`.

> **Локально DaData выключена** ([ImitationSeedCommand :53](../../../app/app/Console/Commands/Imitation/ImitationSeedCommand.php#L53)) → регион берётся из **тега** заявки. Сама лесенка каскада и подмена города работают одинаково; «регион по номеру телефона» (DaData/Россвязь) — 🟦 показываем на прод-прогоне.

---

## Три сценария (через теги — локально воспроизводимо)

| Сценарий | Регион заявки (тег) | Проекты на источнике | Ожидаемая фаза | Город в сделке |
|---|---|---|---|---|
| C-1 точный | Москва (82) | есть проект с регионом [82] | **Фаза 1** (step 1) | Москва (точно) |
| C-2 вся РФ | Татарстан (19) | нет [19], но есть проект [] вся РФ | **Фаза 2** (step 2) | Татарстан (реальный) |
| C-3 непокрытый | Москобл (56) | никто не ловит [56], нет вся РФ | **Фаза 3** (step 3, `region_substituted=true`) | **Московская область (реальный, не подменён)** |

---

## Шаг 3-1 — Сценарий C-1: точное совпадение (Фаза 1)

**🔧 Код:** [LeadRouter :60-69](../../../app/app/Services/LeadRouter.php#L60-L69); город — [RouteSupplierLeadJob :446-450](../../../app/app/Jobs/RouteSupplierLeadJob.php#L446-L450).
**🎬 Действие (лок):** на источнике есть клиент с проектом регион [82]; влить заявку с тегом «Москва».

**📋 ОТЧЁТ:**
| Что | Было | Ожидалось | Стало (факт) | Чем подтверждаем |
|---|---|---|---|---|
| Доставка | — | ушла клиенту с точным регионом (Москва) | _(слот прогона)_ | 📸 Сделки этого клиента (новая) |
| Город в сделке | — | **Москва** | _(слот)_ | 📸 карточка/строка «Город: Москва» |
| Фаза каскада | — | Фаза 1 (точный) | _(слот)_ | 📝 `routing_step=1` (внутр.) |

**❌ Если не так:** доставлено мимо точного региона при наличии точного клиента → дефект каскада.

---

## Шаг 3-2 — Сценарий C-2: вся РФ добирает (Фаза 2)

**🔧 Код:** [LeadRouter :71-82](../../../app/app/Services/LeadRouter.php#L71-L82).
**🎬 Действие (лок):** на источнике НЕТ проекта с регионом [19], но есть клиент с проектом «вся РФ» (`regions=[]`); влить заявку с тегом «Татарстан» (порядковый 19; 16=Мордовия).

**📋 ОТЧЁТ:**
| Что | Было | Ожидалось | Стало (факт) | Чем подтверждаем |
|---|---|---|---|---|
| Доставка | — | ушла клиенту «вся РФ» (добор) | _(слот)_ | 📸 Сделки «вся-РФ» клиента (новая) |
| Город в сделке | — | **Татарстан** (реальный регион) | _(слот)_ | 📸 «Город: Татарстан» |
| Фаза каскада | — | Фаза 2 (вся РФ) | _(слот)_ | 📝 `routing_step=2` |

**❌ Если не так:** заявку не добрали при наличии «вся РФ» / город не Татарстан → дефект.

---

## Шаг 3-3 — Сценарий C-3: непокрытый регион → запасной канал + подмена (Фаза 3)

**🔧 Код:** [LeadRouter :84-93](../../../app/app/Services/LeadRouter.php#L84-L93); подмена — [RouteSupplierLeadJob :452](../../../app/app/Jobs/RouteSupplierLeadJob.php#L452); город=реальный — [:446-450](../../../app/app/Jobs/RouteSupplierLeadJob.php#L446-L450).
**🎬 Действие (лок):** на источнике НИКТО не ловит [56] и нет «вся РФ»; влить заявку с тегом «Москобл».

**📋 ОТЧЁТ:**
| Что | Было | Ожидалось | Стало (факт) | Чем подтверждаем |
|---|---|---|---|---|
| Доставка | — | ушла по запасному каналу (кто-то получил) | _(слот)_ | 📸 Сделки получателя (новая) |
| Город в сделке | — | **Московская область** (НАСТОЯЩИЙ регион, не подменён) | _(слот)_ | 📸 «Город: Московская область» |
| Подмена помечена | — | `region_substituted=true` (фаза 3) | _(слот)_ | 📝 `routing_step=3`, `region_substituted=true` (внутр.) |

**💡 Что внутри:** портал по заявке определяет реальный регион. Если этот регион никто не заказывал и нет тех, кто берёт «всю Россию» — заявка не пропадает, её отдают по запасному каналу. Но клиента не обманывают: в карточке сделки показывается **настоящий город заявки** (Московская область), а внутри ставится пометка, что регион «подменён» при раздаче. Клиент всегда видит правду о заявке.

**❌ Если не так:** город в сделке показан как регион проекта-получателя, а не реальный → обман клиента, дефект; заявка пропала → дефект каскада.

---

## Шаг 3-4 — Регион по телефону (DaData→Россвязь) — 🟦 на прод-прогоне

**🔧 Код:** [LeadRegionResolver :40-67](../../../app/app/Services/LeadRegionResolver.php#L40-L67) (DaData→Россвязь→тег).
**🎬 Действие (прод):** влить заявку с реальным номером телефона без тега → регион определяется **по номеру** (DaData, при отказе — реестр Россвязи).
**🎬 Действие (лок):** не показать (DaData выключена) — локально регион из тега.

**📋 ОТЧЁТ:**
| Что | Было | Ожидалось | Стало (факт) | Чем подтверждаем |
|---|---|---|---|---|
| Регион по номеру | — | определён по телефону (источник `dadata`/`rossvyaz`) | _(слот боевого прогона)_ | 📸 Сделки (город по номеру); 📝 `region_source` |

**💡 Что внутри:** на боевом портал смотрит на номер телефона и сам понимает, из какого региона заявка — даже если регион в заявке не указан. Сначала спрашивает справочник DaData, если не вышло — сверяется с государственным реестром номеров (Россвязь).

**❌ Если не так:** регион по номеру не определяется на проде → каскад работает только по тегу (ухудшение, не блокер).

---

## Verification №3 (выход из пункта)
- [ ] 📸 C-1: сделка у точного клиента, «Город: Москва».
- [ ] 📸 C-2: сделка у «вся-РФ» клиента, «Город: Татарстан».
- [ ] 📸 C-3: сделка по запасному каналу, «Город: Московская область» (реальный, не подменён).
- [ ] 📝 routing_step 1/2/3 соответствуют сценариям; `region_substituted=true` только в C-3.
- [ ] 🟦 на прод-прогоне: регион по номеру телефона (DaData/Россвязь).

## 📸 vs 📝 в этом пункте
- **📸 Скриншот:** Сделки — «Город» в каждом сценарии (Москва / Татарстан / Московская область).
- **📝 Текст:** `routing_step` (фаза каскада), `region_substituted`, `region_source` — глазами не видны.

## Грабли
- Локально регион = **тег** заявки (DaData off); «по телефону» — только прод. Лесенка и подмена города одинаковы.
- Город в сделке — **всегда реальный регион лида**, даже в фазе 3; если показался регион проекта-получателя → дефект.
- Если в разделе Сделки «Город» не выведен в строке — открыть карточку сделки (известная находка F1 про карточку без «Город» — проверить, выводится ли поле).
