Владелец: «в субботу звонки не должны происходить, всё это настраивается из
админки». В панели «Автодозвон» в блоке «Окно звонков» были только часы, а дни
(work_days) править из админки было нечем — их приходилось менять в БД, хотя
планировщик именно по ним и решает, звонить ли сегодня.
Добавлены таблетки дней Пн–Вс тем же механизмом и в том же виде, что в панели
SMS-кампаний: пустой набор не подменяется дефолтом (снял все дни — значит
звонков нет, и в панели это видно). Подсказка в панели теперь честно описывает
поведение: вне окна робот не звонит вообще, попытка уходит на ближайшее
рабочее утро.
Харнесс check_autocall_window.py считает ожидания ИЗ НАСТРОЕК (часы + дни),
поэтому проверка не устаревает при смене графика.
SW: v120 -> v121.
Харнесс прогоняет планировщик с фейковым временем и подменённым push_call_file:
19:58 и 21:03 МСК (инцидент) — звонков нет, 09:00/10:00/18:59 — звонок есть,
воскресенье — нет, суббота — есть. Работает и для файла-кандидата до установки.
Владелец: «звонить вне окна разрешённого нельзя». Окно (часы и дни из панели
«Автодозвон») проверялось только для ручной очереди «Позвонить сейчас», а
автоматическая кампания его игнорировала: 28.09 робот позвонил клиенту в 19:58
и 21:03 МСК (в логе тик сам писал «Outside work hours», но тут же «Found 1
groups» → «Pushed call file»), после чего заказ ушёл в ручную работу.
Теперь перед выборкой кандидатов стоит та же проверка is_work_time(): вне окна
авто-обзвон не запускается, повтор, чей интервал истёк вечером, ждёт ближайшего
рабочего утра. Проверка: /tmp-check_window.py — 7 случаев (вне окна 19:58 и
21:03, внутри 09:00/10:00/18:59, воскресенье, суббота), все PASS; в логе новая
строка «Outside work hours (09:00-19:00 МСК (...)), auto calls skipped».
Клиентская страница согласования:
- приглашение теперь отдаёт deliveryType и pickupCode (public view) — до этого
страница не знала, что заказ самовывозный, и говорила «Доставка заказа /
Согласование доставки» даже для самовывоза; в шапке и hero-тексте выбор по типу
- в блоке с кодом: «Ваш номер доставки 010796» + «Ваш код выдачи 7598»
(номер берём из состава заказа, «ТП\-010796» → «010796»)
- ссылка на заказ теперь «Доставка ТП-010796» вместо «Счет ТП\-010796»,
экранированный дефис из обмена с 1С больше не показывается
SMS: в шаблоны добавились переменные {order}/{номер}/{номер_доставки} (номер
доставки) и {code}/{код}/{код_выдачи} (код выдачи) — работают и в шаблоне
кампании, и в тексте профиля города. Дефолтные тексты самовывоза (и значения в
БД, см. supabase/sms_pickup_code_placeholders.sql) теперь содержат номер и код;
в админке у полей появилась памятка по переменным и предпросмотр с подстановкой.
service-worker v118 → v119
- supabase/log_order_group_change.sql, supabase/order_history_rls.sql: триггер
логирования изменений order_groups и его RLS существовали только на сервере
(в БД работают с 2026-07-29), в git исходников не было — восстановить было нечем
- scripts/__pycache__/*.pyc и DriverDeliveryPlanner.jsx.bak сняты с отслеживания
(артефакты и бэкап, в .gitignore уже были __pycache__/ и *.bak — добавлен *.bak-*)
Раньше текст был зашит в кампании и всегда говорил про доставку; настраиваемый
в админке sms_text_template не читался скриптами вообще. Сначала выяснилось,
что всем 909 pickup-группам уходило «Согласуйте дату доставки по ссылке».
- sms_campaign_settings.sms_text_template_pickup (новая колонка) + значения
sms_text_template = «…дату доставки…», pickup = «…дату самовывоза…»
(supabase/sms_text_templates_pickup.sql = миграция с фактическими текстами)
- sms_first_campaign / sms_second_campaign / sms_paid_storage_campaign:
build_sms_text(group, settings) — шаблон выбирается по og.delivery_type,
{link} подставляется ссылкой; приоритет профиль города (режим «Уведомление»)
→ шаблон кампании → дефолт скрипта
- вторая SMS и платное хранение тоже получили pickup-вариант (кампании были
выключены, поэтому в текстах жил только «доставка»)
- SmsCampaignPanel: два поля ввода (📦 Доставка / 🏪 Самовывоз) + длины и
предпросмотр с подставленной ссылкой + пояснение приоритета
- service-worker v117 (обязательный бамп при деплое фронта)
Проверка цепочки «варианты в меню → цифра → дата в заказе» нашла два дефекта:
1. edge-функция: если дата в call-файл не попала (старый файл/сбой), дата считалась
как «сегодня + цифра − 1» для ОБОИХ типов. Для доставки это сдвигало день на
сутки назад: абонент нажимает «день 1» (робот говорит «завтра»), а в заказ
уезжает сегодня. Теперь fallback повторяет ОЗВУЧЕННУЮ семантику: самовывоз
день 1 = сегодня (сегодня/завтра/послезавтра), доставка день 1 = завтра,
день 2 = послезавтра. Проверено живыми POST на функцию (5 случаев, включая
приоритет chosen_date) — временная группа создана и удалена.
2. планировщик: при пустом «Графике работы» окна подменяют пустой список
дефолтом Пн–Пт, то есть робот предлагал бы дни, когда компания не работает.
Теперь при пустом графике звонок пропускается с понятным предупреждением.
Цепочка «варианты → цифра → дата» проверена на живых окнах: пятница 17:41 → доставка
пн/вт, самовывоз после отсечки 15:30 — только пн (2 варианта), при готовности сегодня
до 15:30 — 3 варианта (пт/пн/вт), цифры 1/2/3 → PICKUP_DAY1/2/3 и DELIVERY_DAY1/2
уходят в диалог и возвращаются как chosen_date.
Канал call-файла строится из настройки autocall_trunk (sms_campaign_settings):
'gsm' — PJSIP/1<номер с 8>@goip5 (линия 1 модуля), 'mmedia' — прежний SIP-транк.
По умолчанию gsm.
У модуля одна SIM → один одновременный звонок: перед постановкой в спул сверяем
занятость каналов транка на АТС и ставим не больше, чем есть свободных линий.
Не успевшие звонки просто ждут следующего тика таймера — иначе они падали бы
«занято» и уезжали в «не ответил». То же для ручных звонков из очереди.
Причина «кучи ошибок 107»: номер попытки читался из строки лога
(attempts = item.get("attempts", 1)), а каждая повторная отправка вставляла
НОВУЮ строку с attempts=1. Условие attempts < max_attempts выполнялось всегда,
поэтому группа уходила в повтор каждые 5 минут бесконечно: заказ ТП-010543
(9780187307) — 461 отправка за 3,5 суток, ни одна не доставлена, в ручную
работу заказ не ушёл.
- номер попытки считается по группе (count строк кампании) и пишется в строку
и в order_groups.sms_attempts;
- в выборке на отправку жёсткий лимит попыток — повтор невозможен даже при
сбое статуса;
- попытки исчерпаны → manual_required («нужен менеджер»), а не send_failed;
- новый шаг 4: группа с исчерпанными попытками, застрявшая в link_ready,
сама уходит в manual_required (самолечение);
- то же исправление во второй кампании (second_sms) — там тот же баг.
- DB: sms_city_rules table + sms_allowed_for_city() function + RLS
- Python: фильтр sms_allowed_for_city в first_sms, second_sms, paid_storage
- Frontend: SmsCityRulesPanel — toggle on/off, период дат, комментарии
- Интеграция в SmsCampaignPanel как вкладка 'SMS по городам'
- Поддержка '*' (все города / все кампании)
- Two numbers in one field (XXX / YYY) → take first 10 digits
- Text in field (Екатерина +7978...) → extract digits only
- 8 prefix → 7 (Russian landline style)
- 10 digits (no country code) → prepend 7
- All 4 SMS scripts patched (first, second, paid_storage)
Scenario 3 (link_ready + next_check expired) now requires EXISTS
in sms_campaign_log. Prevents groups from being moved to
manual_required before first SMS is ever sent.
Bug: sms_manual_campaign.py condition 2 caught first_sms_sent groups after 3h
WITHOUT checking if second SMS was already sent. This caused manual campaign
(5min timer) to move groups to manual_required before second campaign (10min timer)
could send the second SMS.
Fix: added second_sms_sent_at IS NOT NULL check to condition 2.
Now manual only moves groups where second SMS was already sent AND client
still didn't agree after 3 more hours.
Also restored 17 groups that were incorrectly moved to manual_required back
to first_sms_sent so second campaign can send their second SMS.
- LogisticsReadinessBoard: @dnd-kit/sortable for drag-and-drop section reordering
- Drag handle (6-dot grip icon) on left of each section header
- Custom order persists in localStorage (key: logistics-section-order)
- Default: agreed → manual_required → funnel order
- Flat rows (no Button/rounded), matches OrdersTable style
- 7 columns: Клиент | Город | Тип | Дата | Водитель | Статус | Обновлён
- Horizontal scroll: overflow-x-auto + min-w-[1080px]
- Type column wide enough for Самовывоз/Доставка in one line
- OrdersTable: widened Type column (minmax 70px→100px), min-w 920→1080px
for consistent horizontal scroll across tables
- SmsCampaignPanel: 4 sub-tabs (first_sms/second_sms/manual/paid_storage)
- 'Проверить снова' button sets needs_check=true in sms_campaign_log
- Auto-refresh every 30s with toggle
- Elapsed time display per row
- Script: after send, notification_status='sms_sending' (not 'link_ready')
→ double protection against re-sending to same group
- Script: get_sms_to_check includes needs_check=true records regardless of age
- Script: checked_at timestamp on each status check
- DB: needs_check boolean + checked_at timestamptz columns added
- test_mode + test_phone columns in sms_campaign_settings (DB)
- Script reads test_mode from DB settings, not env vars
- Admin panel: toggle Test/Production mode with visual indicator
- test_mode=true (default): SMS only to test_phone
- test_mode=false: SMS to real customers
- Service worker cache v9 → v10 (fixes white screen)
- sms_campaign_log table: full audit trail (phone, sms_id, status, code, attempts)
- sms_campaign_settings: editable by megaadmin (wait times, max attempts, delays)
- New tab 'SMS-кампании' in mega_admin navigation
- State machine: no blocking sleeps, fast cron runs
- Protection: no duplicate SMS within 24h, code 231/132 = limit, not retry