fix(autocall): заявка «позвонить сейчас» с закрытым окном звонка больше не висит вечно в очереди — планировщик закрывает её, карточка честно говорит «звонить больше не о чем» + кнопка «всё равно позвонить»
This commit is contained in:
parent
b965216181
commit
dacd43165c
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }) => {
|
|||
<span>{process.title}</span>
|
||||
</div>
|
||||
<div className="mt-0.5 text-[10px] opacity-80">{process.hint}</div>
|
||||
{process.phase === "unreachable" && (
|
||||
<div className="mt-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCallNow}
|
||||
disabled={sending || processing}
|
||||
className="rounded-lg border border-[var(--color-warning)] px-2.5 py-1 text-[11px] font-medium text-[var(--color-warning)] hover:bg-[rgba(245,158,11,0.08)] disabled:opacity-50"
|
||||
>
|
||||
{sending ? "Отправляем заявку…" : "📞 Всё равно позвонить"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{process.phase === "done" && callAfter && (
|
||||
<div className="mt-1.5 flex flex-wrap items-center gap-2">
|
||||
<Badge tone={(OUTCOME_META[callAfter.outcome] || {}).tone || "neutral"}>
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue