diff --git a/scripts/autocall_scheduler.py b/scripts/autocall_scheduler.py index 961fdb6..4eed0b7 100644 --- a/scripts/autocall_scheduler.py +++ b/scripts/autocall_scheduler.py @@ -603,6 +603,16 @@ def process_queue(conn, settings): if push_call_file(g, settings, attempt_no, is_test_call=is_test, manual=True): mark_queue_processed(conn, q["queue_id"]) pushed += 1 + else: + # Звонить не о чем: окно звонка закрыто (например, заказ готов + # раньше, чем «сегодня + 2 рабочих дня» самовывоза). Закрываем + # заявку навсегда — иначе она молча крутится в очереди, и в + # карточке вечно висит «в очереди, позвоним в течение минуты». + log.warning( + f"Queue item {q['queue_id']} (group {group_id}): окно звонка закрыто, " + "заявка закрыта — роботу нечего предлагать" + ) + mark_queue_processed(conn, q["queue_id"]) attempt_map[group_id] = attempt_no + 1 return pushed diff --git a/src/components/orders/AutocallStatusCard.jsx b/src/components/orders/AutocallStatusCard.jsx index 7f36c3b..1d95781 100644 --- a/src/components/orders/AutocallStatusCard.jsx +++ b/src/components/orders/AutocallStatusCard.jsx @@ -130,9 +130,26 @@ export const AutocallStatusCard = ({ order, userRole }) => { return Date.now() - new Date(queueItem.requested_at).getTime() <= 24 * 60 * 60 * 1000 ? queueItem : null; }, [queueItem]); + // Заявка висит непринятой больше часа в рабочее время — планировщик молча + // пропускает её (окно звонка закрыто: по графику роботу нечего предлагать). + // Показываем честную причину вместо «в очереди». + const callUnreachable = useMemo(() => { + if (!recentQueue || recentQueue.processed_at || callAfter) return false; + const age = Date.now() - new Date(recentQueue.requested_at).getTime(); + if (age < 60 * 60 * 1000) return false; + // Рабочее время сейчас? + const workDays = (settings?.work_days || "").toString().split(",").map((s) => parseInt(s, 10)).filter(Number.isFinite); + const startHour = Number(settings?.work_hours_start ?? 9); + const endHour = Number(settings?.work_hours_end ?? 20); + const msk = new Date(Date.now() + (3 * 60 + new Date().getTimezoneOffset()) * 60000); + const dayOk = workDays.length === 0 || workDays.includes(msk.getDay()); + const hourOk = msk.getHours() >= startHour && msk.getHours() < endHour; + return dayOk && hourOk; + }, [recentQueue, callAfter, settings]); + const process = useMemo( - () => describeCallProcess({ queueItem: recentQueue, callAfter, settings: settings || {} }), - [recentQueue, callAfter, settings], + () => describeCallProcess({ queueItem: recentQueue, callAfter, settings: { ...(settings || {}), call_unreachable: callUnreachable } }), + [recentQueue, callAfter, settings, callUnreachable], ); const processing = process?.phase === "pending" || process?.phase === "pending_later" || process?.phase === "dialing"; @@ -247,6 +264,18 @@ export const AutocallStatusCard = ({ order, userRole }) => { {process.title}
{process.hint}
+ {process.phase === "unreachable" && ( +
+ +
+ )} {process.phase === "done" && callAfter && (
diff --git a/src/services/autocallProcess.js b/src/services/autocallProcess.js index 18f94db..8f2a512 100644 --- a/src/services/autocallProcess.js +++ b/src/services/autocallProcess.js @@ -133,6 +133,19 @@ export const describeCallProcess = ({ queueItem, callAfter, settings = {}, now = const soon = isCallTimeNow({ work_hours_start: startHour, work_hours_end: endHour, work_days: workDays }, now); const ageMinutes = (now.getTime() - new Date(queueItem.requested_at).getTime()) / 60000; + // Заявку взяли в работу, но call-файл не создан: по графику окна звонка для + // этого заказа уже нет (например, окно самовывоза закрылось — заказ готов + // раньше, чем «сегодня + 2 рабочих дня»). Планировщик молча пропускает её + // каждый тик — покажем это честно, а не «в очереди». + if (settings.call_unreachable) { + return { + phase: "unreachable", + tone: "warning", + title: "Звонить по этому заказу больше не о чем", + hint: `Робот каждый раз пропускает заявку: по графику у заказа не осталось доступных дней для предложения — ${requested}. Дальше вручную: позвоните сами или подтвердите день в карточке.`, + }; + } + if (soon) { // В рабочее время заявку обязаны взять на ближайшем тике (раз в минуту). if (ageMinutes > 15) {