df8c4720cb
Голый шов. Правило Т84 «недозвон не затирает состоявшийся разговор» жило в двери ItogPoNomeru, и восемь сторожей доказывали, что дверь работает. Но все восемь ходили ЧЕРЕЗ дверь, а дверью можно не воспользоваться: запрета писать МИМО не было ни одного. Итог по номеру сегодня не пишет никто, значит первым пишущим будет следующая смена — она увидит у модели поле outcome раньше, чем найдёт дверь, напишет в него напрямую, и правило рухнет молча. Дверь осталась бы целой и никем не использованной. Защита разделена по УСТРОЙСТВУ, а не по тексту, и белого списка «кому можно» нет ни в одной половине — такой список сам стал бы дырой. Пол в самой модели ловит любую запись через объект: присваивание полю, fill, update у строки, save. Имя переменной он не читает вовсе — он стоит на самой записи, поэтому главная слабость текстового поиска закрыта не текстом, а местом. Проверяется смысл перехода, а не автор: дверь поблажек не имеет и проходит лишь потому, что запрещённого перехода не делает. Обход кода ловит то, чего пол не видит физически: массовую правку и запросы построителем — Eloquent на них событий не шлёт. Запрет действует на всех без исключений, включая саму дверь. Раскладка исходов НЕ задвоена: обе половины читают одну настройку. Повторено только применение правила — модели запрещено зависеть от служб, у слоя Model в настройке слоёв разрешённых зависимостей ноль. Два места связаны сторожем, который обходит все 64 пары исходов и требует одинакового ответа. Чего не ловит НИ ОДНА из половин, названо в бумагах: правку прямым SQL мимо портала, имя таблицы или столбца, собранное из кусков, и код вне app. Первое лечится только замком в самой базе — это правка канона схемы и вопрос владельцу, поэтому названо, а не сделано молча. Сторожа показаны красными. Настоящая запись мимо двери положена в САМ обходимый код, а не в песочницу: сторож назвал файл, строку 14 и само выражение. Снятие пола дало три красных, включая связку — пара transferred и no_answer. Возврат доказан слепком со снятием невидимых знаков конца строки. Заодно подписи двух значений в настройке: владелец закрыл обе развилки решениями Р120 и Р121 от 06.08.2026, оба совпали с поставленным. Значения не менялись, переписаны только пояснения, и рядом с каждым названо то, чего решение НЕ закрывает. Прогоны: обзвон и сделки 442, зелёных 441, красных ноль. Мои сторожа 32 из 32. Статанализ ноль, deptrac ноль нарушений. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
117 lines
7.5 KiB
PHP
117 lines
7.5 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
/**
|
||
* Настройки обзвона — ОДНО место, где лежат решения, которые дороже всего
|
||
* переигрывать рассыпанными по коду (З-2.4, решения владельца Р112/Р113).
|
||
*
|
||
* 🔴 Зачем файл вообще. Восемь исходов Т21/Т65 живут В ОДНОМ столбце
|
||
* `obzvon_number_results.outcome`, и замок в базе пропускает любой из восьми.
|
||
* Значит «недозвон верхнюю строку не меняет» (Т84) базой НЕ охраняется: тот,
|
||
* кто запишет недозвон туда же, куда пишет остальные семь, затрёт удачный
|
||
* разговор молча — столбец один, значение разрешено, база примет. Единственное
|
||
* место, где эта разница вообще существует, — здесь.
|
||
*
|
||
* Кто это читает: App\Services\Obzvon\RaskladkaIskhodov (и только он).
|
||
*/
|
||
return [
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Раскладка восьми исходов на два рода (Р112)
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
| «Про номер» — исход самого номера. Верхнюю строку карточки берёт
|
||
| ПОСЛЕДНИЙ ПО ВРЕМЕНИ из них, и каждый попадает в ленту разговоров.
|
||
|
|
||
| «Про попытку» — исход одной попытки набора. Верхнюю строку НЕ меняет,
|
||
| в ленту не попадает, сворачивается в счётчик «не дозвонились: N раз».
|
||
|
|
||
| 🔴 Ровно одно исключение, и оно нарочное: когда состоявшегося разговора
|
||
| НЕ БЫЛО НИ РАЗУ, верхняя строка обязана сказать «не дозвонились» — иначе
|
||
| двенадцать попыток дадут пустую карточку. Поэтому исход «про попытку»
|
||
| ставится как ЗАПАСНОЙ: пишется только поверх пустоты или самого себя и
|
||
| никогда поверх исхода «про номер». Правило исполнено в одном месте —
|
||
| App\Services\Obzvon\ItogPoNomeru::zapisat.
|
||
|
|
||
| Переиграть раскладку = переставить слаг из списка в список. Минута.
|
||
| Восемь слагов — канон в App\Models\ObzvonNumberResult::OUTCOMES и в
|
||
| CHECK-замке `chk_obzvon_number_results_outcome`; здесь только раскладка.
|
||
|
|
||
*/
|
||
'raskladka' => [
|
||
|
||
'pro_nomer' => [
|
||
'transferred', // переведено
|
||
'transfer_failed', // переведено неуспешно
|
||
'callback', // перезвонить, дата и время
|
||
'email_sent', // письмо клиенту отправлено
|
||
'not_target', // не целевой
|
||
'refused', // отказ
|
||
'aggression', // агрессия
|
||
],
|
||
|
||
'pro_popytku' => [
|
||
'no_answer', // недозвон
|
||
],
|
||
],
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Карточка сделки — два вопроса, ЗАКРЫТЫЕ владельцем 06.08.2026 (З-2.4)
|
||
|--------------------------------------------------------------------------
|
||
|
|
||
| 🟢 Оба значения ниже — не «пока так», а **решения владельца**: Р120 и Р121
|
||
| от 06.08.2026. Переспрашивать закрытое не нужно.
|
||
|
|
||
| Менять их можно, но только новым решением владельца: числа стоят в одном
|
||
| месте нарочно, чтобы правка стоила минуту, а не чтобы её делали походя.
|
||
|
|
||
*/
|
||
'kartochka' => [
|
||
|
||
/*
|
||
* Где кончается «хоть секунда разговора» (Т84а).
|
||
*
|
||
* 🟢 РЕШЕНИЕ ВЛАДЕЛЬЦА Р120 от 06.08.2026: разговором считается контакт
|
||
* от ОДНОЙ секунды. Закрыто, переспрашивать не нужно.
|
||
*
|
||
* Разговор попадает в ленту, когда трубку сняли (`answered`) И первое
|
||
* плечо длилось не меньше этого числа секунд.
|
||
*
|
||
* Развилки «0,4 секунды» не существовало вовсе: длительность в базе —
|
||
* целые секунды (`leg1_duration_sec INTEGER`). Владелец выбирал из
|
||
* «одна, три или пять» и отрезал три и пять по доводу: короче порога
|
||
* сброс сливается с недозвоном и становится невидимым, а сброс — это
|
||
* ОТВЕТ человека, тогда как недозвон — его отсутствие.
|
||
*
|
||
* 🪤 ЧЕГО РЕШЕНИЕ НЕ ЗАКРЫВАЕТ: при пороге в секунду лента длиннее, чем
|
||
* при любом другом, — короткие сбросы тоже строки, и сворачивать их
|
||
* никто не назначал; случай «соединилось и оборвалось до первого звука»
|
||
* ни разу не замерен на живых звонках.
|
||
*/
|
||
'minimalnaya_dlitelnost_sec' => 1,
|
||
|
||
/*
|
||
* Сколько ПРОШЛЫХ списков показывать в карточке (Р113).
|
||
*
|
||
* 🟢 РЕШЕНИЕ ВЛАДЕЛЬЦА Р121 от 06.08.2026: показываем ВСЕ прошлые
|
||
* списки. Закрыто, переспрашивать не нужно. Довод: Р113 требует, чтобы
|
||
* прошлое было ВИДНО, и прятать часть истории по своему усмотрению мы
|
||
* не вправе.
|
||
*
|
||
* Тот же номер новым списком заводит НОВЫЙ итог (замок
|
||
* `UNIQUE (tenant_id, campaign_id, phone)`), прежний остаётся историей.
|
||
*
|
||
* null — показывать все. Число N — показывать N самых свежих списков.
|
||
*
|
||
* 🪤 ЧЕГО РЕШЕНИЕ НЕ ЗАКРЫВАЕТ: карточка растёт без предела — у номера
|
||
* в десятке списков лента вытянется. И отдельно: итог привязан к
|
||
* СПИСКУ, а не к человеку, поэтому при смене владельца номера в
|
||
* карточке смешается история двух РАЗНЫХ людей. Это не обсуждали вовсе.
|
||
*/
|
||
'kampaniy_v_kartochke' => null,
|
||
],
|
||
];
|