Строка листа 4.5, Этап 4 Task 4.
Раньше имя, которое клиент выключил сам или которое отключили за долг, можно
было вернуть только одним способом: заказать заново, снова приложив документы и
снова дождавшись согласования у МТС. Теперь у клиента есть кнопка: она берёт
плату за месяц вперёд и включает имя сразу — решение владельца. Денег не хватает
— портал говорит, СКОЛЬКО именно не хватает, и не трогает ни имя, ни кошелёк.
Документы заново не запрашиваются: заявка уже согласована, сканы на месте.
Ключ списания у кнопки тот же, что у ночного работника, поэтому за один
календарный месяц имя оплачивается один раз — кто бы его ни включал. Сумма
нигде не зашита, берётся из карточки имени и вырастет сама, когда подключим
остальных операторов.
Плана оказалось мало ЧЕТЫРЕЖДЫ, и все четыре раза — про деньги.
В-143: план не смотрел, КТО отключил имя. Имя отключает и владелец руками, и оно
у МТС погашено; клиент нажал бы кнопку, портал взял бы с него плату, а имя не
работало бы. Это ровно то, что работнику запретили в прошлой задаче. Кнопка
теперь смотрит в ту же графу «почему отключено» и при включении её гасит.
В-144: слово «отменено» стоит и у имени, которое когда-то работало, и у заявки,
которую клиент отозвал ещё на согласовании. Вторую план включил бы как рабочее
имя и списал за неё деньги — за имя, которого у оператора нет. Отличаю по
отметке согласования: её ставит ровно одно место, кнопка одобрения у владельца.
В-145: имя, оплаченное вперёд, план списал бы второй раз — а при пустом кошельке
ещё и отказался бы включить имя, за которое уже заплачено. Смотрю на «оплачено
до»: срок в будущем — включаю без списания и срок не двигаю.
В-146: план отдавал экрану решение «показывать ли кнопку». После первых двух
правок правило перестало быть про один признак, и оно переехало на сервер: адрес
имени отдаёт готовые «можно ли включить» и «сколько спишется», экран не считает.
Отдельно В-148 — его поймал только браузер, мимо прошли 12 серверных и 66
фронтовых тестов. Экран писал «плата за текущий срок уже внесена» у имени,
отключённого за долг с апреля. Ноль в сумме приходил не от оплаты, а от
песочницы — одно значение с двумя разными причинами. Развёл: сумма считается
только по сроку оплаты, песочница гасит сам денежный шаг.
Проверено вырезанием, восемь вырезов, все вернуты, каждый покраснел там, где
вырезан: проверка денег, отбор по причине отключения, отметка согласования,
ветка «оплачено вперёд», ключ месяца, песочница в решении о сумме, кнопка на
экране, учёт «можно ли» на экране.
Живой прогон под боевой ролью crm_app_user, семь нажатий: 50 ₽ при плате 100 —
отказ «Не хватает 50.00 ₽», не тронуто ничего; пополнил до 550 — имя работает,
списано ровно 100 ₽, оплачено до 29.08; имя владельца при 5000 ₽ — отказ, деньги
целы; неодобренная заявка при 5000 ₽ — отказ, деньги целы; второе нажатие —
«имя уже работает», в движениях одно списание. Пара на пометку клиента: без неё
то же нажатие не делает ничего, с ней — списывает и включает.
Живой прогон в браузере: у имени с долгом видна кнопка и строка «Спишется
2500.00 ₽ — плата за имя за месяц»; нажал — карточка стала «Ваше имя: MYSHOP
(активно)». У имени, отключённого владельцем, кнопки нет вовсе.
Прогоны: клиентские СМС 275/275 (11 пачек, все с первой попытки), приём лидов
17/17, фронт 1683 + 3 пропущенных, phpstan ровно 2 чужие давние, pint чисто.
Проверка типов фронта: 5 ошибок в 5 файлах вместо прежних 8 в 6 — задал явный
тип ответа в спеке, который и так правил, и три давние ошибки ушли вместе с
моими. Схема базы не менялась, миграций нет.
Две разные защиты, и они не взаимозаменяемы.
Жёсткая: у заказа есть ключ, который экран придумывает при открытии формы. Тот же
ключ = тот же самый заказ: двойной клик, обрыв связи, повтор браузера возвращают
первую рассылку и денег не трогают. Молча — человек ничего нового не просил.
Гонку добивает уникальный индекс в базе, нарушение ловится и отдаёт первую рассылку.
Мягкая: заказ другой, но текст и источник те же, и десяти минут не прошло. Здесь
решает человек — сервер отвечает вопросом «вы уже это запускали, отправить ещё раз?»,
а с подтверждением рассылка уходит.
Строки приёмочного листа 1.18-1.20 (серверная часть). Защита проверена вырезанием:
убрать проверку ключа — 2 красных теста.
Мягкая защита закономерно задела два прежних теста, где рассылка с тем же текстом
создаётся дважды подряд намеренно, — им дописано подтверждение.
ClientSms 156/156, приём лидов 17/17, phpstan по своим файлам чисто.
Имя отправителя Лидерра регистрирует у МТС от лица клиента, поэтому письмо-разрешение
нужно всегда. Собрал генератор бланка так, чтобы он покрывал все случаи и всегда
отдавал целый документ — заполненный, где есть данные, и с прочерками, где их нет.
- ConsentLetterBuilder — единый источник текста письма на все 8 комбинаций
«объект (юр.наименование/название ИП/товарный знак/домен) × правообладатель
(юрлицо/ИП/физлицо)» строго по образцам МТС. Паспорт физлица не храним — прочерк.
- ConsentDocxWriter — рендер в РЕДАКТИРУЕМЫЙ Word (.docx) вместо PDF, чтобы клиент
дописал недостающее; акцент про паспорт («должны совпадать с документом на право»).
- consentForm(): убрана жёсткая блокировка (бланк отдаётся всегда), добавлен выбор
правообладателя owner_type/owner_name — домен/знак могут быть на физлице даже
у клиента-юрлица/ИП, тогда письмо от этого физлица.
- Экран: подсказки «как назвать имя» под каждый вид, общее правило (до 11 знаков,
латиницей), радио «на кого зарегистрирован домен/знак» + поле ФИО, кнопка бланка
активна после ввода имени, ярлык «Скачать бланк разрешения (Word)».
- consent-form.blade.php удалён (заменён docx); phpoffice/phpword ^1.4 в composer.
Тесты: бэкенд ClientSms 116, фронт SMS-спеки 44 — зелёные; pint чисто.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Имя у оператора регистрирует Лидерра от лица клиента, поэтому от клиента два
скана: подписанное разрешение и документ-основание. Оба обязательны, галочка
согласия убрана.
- Бланк разрешения — PDF по нашему образцу «Разрешение-домен УНИВЕРСАЛЬНОЕ»:
Правообладатель = клиент, Пользователь = наш ИП из legal_entities is_default.
4 вида имени домен/юрлицо/ИП/товарный знак — у каждого своё основание права.
- Физлицо: домен часто на физлицо — гейт по ФИО, паспорт вписывается от руки,
паспорт не храним 152-ФЗ.
- Две колонки client_sms_senders: doc_* документ-основание, consent_doc_*
подписанное разрешение.
- Админка: скачивание обоих сканов, вид .../document/basis и .../document/consent.
- Гейт реквизитов requisites_ready; эндпоинт GET /api/sms/sender/consent-form.
Тесты: ClientSms backend 95, фронт СМС 43, pint чисто.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Жизненный цикл имени: клиент requestSender (freeze первого месяца) / disableSender;
админ approve (charge+release, active, paid_until+1мес) / reject / disable. Помесячная
оплата ChargeSmsNameFeeJob (проверка средств ДО списания, долг>29 дней→suspended,
идемпотентно по external_key), расписание в console.php. Авто-рассылка: DealSmsObserver
(защитный, freshness-guard, НИКОГДА не роняет приём лида) → SendAutoSmsForDealJob
(best-effort, идемпотентно по deal_id, песочница/деньги/маршрут). Действующее имя
кампании = active-имя тенанта иначе liderra.ru.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>