496008d1be2a9f5481e84a2b1e5ca3c33e5bf738
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2b401298d8 |
feat: приём записей клиента для обучения робота обзвона — З-2.3
Клиент отдаёт нам записи СВОИХ холодных разговоров. Это голоса живых людей, которые об этом не знают и согласия нам не давали. До этой правки принять их было негде и нечем: положить так, чтобы клиент А не открыл запись клиента Б, было некуда; срока жизни у записей не было; обращение к чужому голосу не оставляло следа. Что заведено: - таблица obzvon_materialy_klienta — канон в db/schema_modules.sql раздел 24, журнал схемы v9.70. Изоляция клиентов защитой по строкам: ENABLE + FORCE, политика с USING и WITH CHECK. DELETE не выдан никому — строка остаётся носителем надписи «удалены по сроку хранения»; - ДВА срока, а не один — решение владельца Р82: звук 30 дней, расшифровка шесть месяцев. Обе даты истечения NOT NULL: строка без даты означает голос, которого не найдёт ни одна чистка. Сами сроки — в настройках портала, не в коде; - два следа удаления врозь: команда чистки из З-2.2 обязана уметь стереть звук, не тронув текст. Два замка не дают строке врать «стёрто», пока файл жив; - закрытое хранилище obzvon_materialy: без ключа url и с serve=false, корень под storage/app/private. Публичной ссылки на чужой голос не появится даже по ошибке; - служба MaterialyKlientaService: приём с отклонением чужого формата и битого файла, открытие с явной сверкой клиента поверх защиты по строкам, счёт записей без нижней границы — три берём, двадцать берём, ноль пускаем вторым путём; - отказ MaterialNePrinyat отделяет наш сочинённый текст от дословного показания системы видимой границей. Настоящая причина не выбрасывается; - след в журнале ПДн pd_processing_log готовым сервисом PdAuditLogger — на приём и на каждое открытие. Стирает НЕ эта правка, а З-2.2, и она не построена. До её постройки материалы не исчезнут — это известно и так задумано. При накатке на боевой ОБЯЗАТЕЛЕН перезапуск db/03_service_bypass_policies.sql: новой RLS-таблице мало политики и прав, иначе служебная роль молча правит НОЛЬ строк. Тронут один чужой сторож: SchemaDeltaTest, счётчик таблиц канона модулей 42 в 43. Так же его правили соседи на З-1.1 и З-1.3 — счётчик обязан расти вместе с каноном, иначе прогон красный у всех. Осталось открытым: от какой даты считать полгода — считаем от загрузки, столбец recorded_at заведён под будущий ответ владельца. 24 сторожа, все 24 показаны красными двенадцатью ножами. Полный прогон 4954 проверки, 4950 зелёных, 4 пропущено, красных 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |