Commit Graph

3 Commits

Author SHA1 Message Date
Дмитрий 2f0ef8e868 feat,обзвон: тариф звонка переехал в базу — админ правит цену без программиста
Беда: правило цены звонка уже написано и работает, а самих двух цифр — за снятую
трубку и за начатую минуту — держать было негде и менять некому. Чтобы поправить
цену, нужен был программист и новая сборка.

Заведена таблица obzvon_tariffs: строка ровно одна на весь портал, замок
CHECK id = 1, деньги целыми копейками — те же единицы, что у строки звонка.
Ручка админки GET и PUT /api/admin/obzvon/tariff правит обе цифры разом:
половина новой пары с половиной старой давала бы тариф, которого никто не назначал.

🔴 Обе цифры назначены владельцем ВСЛЕПУЮ: сколько нам самим стоит звонок, ни разу
не измерено. Оговорка написана в четырёх местах вплотную к самим числам и уходит
в ответ ручки, чтобы её видел человек на экране, а не только программист в коде.

🔴 Смена тарифа НЕ пересчитывает вчерашние звонки: цена и снимок тарифа лежат в
самой строке звонка. Здесь только «сколько будет стоить следующий».

Мусор в цене не принимается: отрицательная, пустая, нечисловая и с третьим знаком
после точки — третий знак молча пропал бы копейкой. Правка оставляет след в
saas_admin_audit_log — кто, когда, с чего на что.

Канон схемы — db/schema_modules.sql раздел 23, журнал — запись v9.69.
Сторожа — app/tests/Feature/Obzvon/ObzvonTariffTest.php, все показаны красными.

План: docs/superpowers/plans/2026-08-04-obzvon-pod-klienta.md §З-1.3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 13:42:34 +03:00
Дмитрий 30ca789507 feat,обзвон: место под звонок — таблица попыток и единственный итог по номеру
Задача З-1.1 плана «Обзвон под клиента». До этой правки фичи в портале не было
вовсе, и звонок было некуда положить: нечего списывать, нечего показать клиенту и
нечем ответить через год на вопрос «за что вы взяли с меня деньги в августе».

Заведены две таблицы, обе с защитой по клиентам ENABLE + FORCE:

- obzvon_calls — одна попытка набора. По номеру их бывает до двенадцати, и каждая
  своя строка со своим исходом. Держит два плеча врозь: робот с человеком и
  человек с менеджером после перевода, у каждого своя длительность и свой признак
  «состоялось». Держит два срока хранения и два следа удаления: звук месяц, текст
  три месяца, дальше живут сухие итоги.
- obzvon_number_results — единственный итог на всю работу с номером. Именно он
  стоит в карточке сделки, а не последняя попытка. Замок — UNIQUE по клиенту,
  кампании и номеру: двенадцать недозвонов дают ОДИН итог, а не двенадцать.

Расшифровка лежит в той же строке звонка. Отдельного хранилища под неё нет
намеренно — решение Р50 сняло бессрочную обезличенную расшифровку.

Имя своё, а не call_recordings из закомментированного чертежа: чертёж был про
телефонию вообще и не знает ни статуса обзвона, ни сроков со следом удаления, ни
«почему звонили», ни двух плеч, ни попыток с итогом. Чертёж не тронут.

Цена звонка запоминается в строке вместе со снимком тарифа, а не вычисляется из
действующего тарифа при показе: иначе смена тарифа задним числом перепишет
вчерашние счета. Образец — lead_charges.tier_no + price_per_lead_kopecks. Деньги
целыми копейками.

Строку звонка никто не удаляет: право DELETE не выдано ни одной роли. Вместе со
строкой ушли бы деньги, а спросить «за что вы взяли» клиент вправе и через год.

Канон — db/schema_modules.sql, новый раздел 23. Тело db/schema.sql таблиц не
получает: оно исполняется первой миграцией, и таблицы дельта-миграций в него
класть нельзя — это прямо запрещает сторож канона. В db/schema.sql добавлена
только пометка над закомментированным чертежом call_recordings: чертёж не
использован и не будет, модуль живёт в obzvon_*, и перечислено, чего чертёж не
знает. Чтобы следующая смена не воскресила его по недоразумению.

Журнал схемы — запись v9.68, номер свободен, столкновения нет.

Проверено: 21 сторож, в том числе изоляция клиентов живым запросом из-под роли
crm_app_user, а не глазами по миграции; служебная роль после перезапуска
db/03_service_bypass_policies.sql правит именно ту строку, а не «успешно ноль»;
откат миграции хвостов не оставляет; squawk с конфигом проекта чист; статанализ
по новым файлам ноль. Каждый сторож показан красным.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 11:01:19 +03:00
Дмитрий bffca85399 docs+fix: канон схемы догнал миграции — 39 таблиц описаны, сверка считает обе стороны
Канон знал 82 таблицы и 995 столбцов, а база — 121 и 1470: не описаны были
рекламный модуль, телеграм-рассылки и весь отдел продаж.

Главное, что выяснилось по дороге: db/schema.sql не документ, а исполняемый
файл — миграция 0001_01_01_000000_load_initial_schema заливает его целиком
первым шагом сборки. Перенести туда DDL модулей нельзя: те же таблицы создают
дельта-миграции, их 77 и 49 из них без стражей. Сборка с нуля падала дважды.
Поэтому канон разбит на два файла: schema.sql исполняется, новый
schema_modules.sql только описывает.

Проверено не глазами:
- сборка с нуля 148 из 148 DONE;
- столбцы 1470 против 1470, ноль расхождений по имени, типу, длине и NULL;
- все четыре счётчика расхождений инструмента — ноль;
- schema.sql отдельно исполняется без единой ошибки, ограничения 495=495,
  политики 64=64;
- статанализ 0.

Инструмент сверки усилен: считал только одну сторону и потому не видел
переименования jivo_chat_id в chat_id — старое имя жило в каноне месяц.
Теперь обе стороны, канон из нескольких файлов, понимание RENAME/DROP COLUMN
и DROP TABLE. Починен разбор переносов строки внутри определения столбца.

Сторож SchemaDeltaTest дополнен: файл модулей обязан быть на месте и описывать
39 таблиц, а тело schema.sql их не содержать. Принят вырезанием.

Найдено и НЕ исправлено, нужно решение владельца: задвоенный указатель на
deals и пять политик без NULLIF в миграциях. Оба помечены в тексте канона.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 12:53:45 +03:00