fix(autocall): день робота → дата без сдвига; окно не считаем при пустом графике

Проверка цепочки «варианты в меню → цифра → дата в заказе» нашла два дефекта:

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.
This commit is contained in:
Hermes 2026-09-25 16:56:21 +02:00
parent 21eade5d3f
commit 7e719e6b43
2 changed files with 31 additions and 3 deletions

View File

@ -511,6 +511,23 @@ def build_call_file(group, settings, attempt_no, is_test_call=False, manual=Fals
scenario = group.get("scenario") or resolve_scenario(group.get("created_at"))
call_type = resolve_call_type(group)
# График работы (админка) — источник рабочих дней. Если нужный список ПУСТ
# (все дни сняты или настройка не заполнена), окно считать нечем: сами функции
# окна подменяют пустой список дефолтом «Пн–Пт», и робот начал бы предлагать
# дни, когда компания не работает. Такой звонок лучше пропустить и сказать в логе.
if call_type == "pkp" and not settings.get("pickup_work_days"):
log.warning(
"Skip group %s: в «Графике работы» не заполнены дни самовывоза — окно не посчитать",
group.get("id"),
)
return None
if call_type == "dlv" and not settings.get("delivery_work_days"):
log.warning(
"Skip group %s: в «Графике работы» не заполнены дни доставки — окно не посчитать",
group.get("id"),
)
return None
# Самовывоз: конкретные даты передаём в диалог явно — робот называет их
# голосом и возвращает выбранную дату, а не абстрактный «день 1/2».
pickup_days = []
@ -532,6 +549,11 @@ def build_call_file(group, settings, attempt_no, is_test_call=False, manual=Fals
group.get("sms_sent_at") or group.get("created_at"),
settings.get("delivery_work_days"),
)
if not delivery_days:
log.warning(
"Skip group %s: пустой график доставки — окно не посчитать", group.get("id")
)
return None
lines = [
f"Channel: {trunk_channel(settings, dial)}",

View File

@ -171,12 +171,18 @@ Deno.serve(async (request) => {
// ── confirmed*: записываем слот ──
if (["confirmed", "confirmed_kept_previous", "confirmed_silent"].includes(body.outcome)) {
// День называет сам диалог (chosen_date) — робот перечисляет конкретные
// дни окна самовывоза, поэтому «1/2/3» само по себе дату не задаёт.
// Если дата не пришла (старые call-файлы) — считаем по цифре дня.
// дни окна, поэтому «1/2/3» само по себе дату не задаёт.
// Если дата не пришла (старый call-файл, пустой график) — восстанавливаем
// по цифре ТАК, КАК РОБОТ ЕЁ ОЗВУЧИЛ: самовывоз «день 1» = сегодня
// (сегодня/завтра/послезавтра), доставка «день 1» = завтра, «день 2» =
// послезавтра (доставка в день готовности не бывает). Раньше для обоих
// типов бралось «сегодня+цифра-1» — доставке это сдвигало день на сутки назад.
const chosenDate = /^\d{4}-\d{2}-\d{2}$/.test(body.chosen_date || "")
? (body.chosen_date as string)
: null;
const dayOffset = body.dtmf_day === 3 ? 2 : body.dtmf_day === 2 ? 1 : 0;
const dayOffset = callType === "pkp"
? (body.dtmf_day === 3 ? 2 : body.dtmf_day === 2 ? 1 : 0)
: (body.dtmf_day === 2 ? 2 : 1);
const deliveryDate = chosenDate || addDays(new Date(), body.dtmf_day ? dayOffset : 1);
const timeSlot = body.dtmf_half ? HALF_LABEL[body.dtmf_half] : null;
const dayLabel = chosenDate