fix(autocall): доставка предлагает реальные рабочие дни графика (пт → пн/вт), эскалация по настройкам админки, WAIT FOR DIGIT и «0 — менеджер»
This commit is contained in:
parent
3706f64ca7
commit
44f58ce9e7
|
|
@ -27,16 +27,26 @@ OUT="${RES}-${UNIQ}.txt"
|
||||||
say () { echo "$1"; read -r resp; echo "$(date +%T) $1 -> $resp" >> "$LOG"; RESP="$resp"; }
|
say () { echo "$1"; read -r resp; echo "$(date +%T) $1 -> $resp" >> "$LOG"; RESP="$resp"; }
|
||||||
digit () { # $1=озвучка, $2=таймаут мс -> DIGIT
|
digit () { # $1=озвучка, $2=таймаут мс -> DIGIT
|
||||||
say "GET DATA $1 ${2:-9000} 1"
|
say "GET DATA $1 ${2:-9000} 1"
|
||||||
DIGIT=$(echo "$RESP" | sed -n 's/.*result=\([0-9]*\).*/\1/p')
|
# GET DATA отдаёт саму цифру (200 result=1). Тишина приходит как «(timeout)»,
|
||||||
|
# а нажатый «0» — как result=0: раньше их путали и «0» (позвать менеджера)
|
||||||
|
# был неотличим от молчания.
|
||||||
|
case "$RESP" in
|
||||||
|
*"(timeout)"*|*"result=-1"*) DIGIT="" ;;
|
||||||
|
*) DIGIT=$(echo "$RESP" | sed -n 's/^200 result=\([0-9]*\)$/\1/p') ;;
|
||||||
|
esac
|
||||||
[ -n "$DIGIT" ] && ANY_DIGIT=1
|
[ -n "$DIGIT" ] && ANY_DIGIT=1
|
||||||
}
|
}
|
||||||
|
|
||||||
wait_digit () { # $1=таймаут мс -> DIGIT (после EXEC Playback; 0 у Asterisk = тишина)
|
wait_digit () { # $1=таймаут мс -> DIGIT (после EXEC Playback; 0 у Asterisk = тишина)
|
||||||
say "WAIT FOR DIGIT ${1:-9000}"
|
say "WAIT FOR DIGIT ${1:-9000}"
|
||||||
DIGIT=$(echo "$RESP" | sed -n 's/.*result=\([0-9]*\).*/\1/p')
|
# WAIT FOR DIGIT возвращает ASCII-код нажатой цифры (49 = «1»), 0 = тишина,
|
||||||
if [ "$DIGIT" = "0" ] || [ -z "$DIGIT" ]; then
|
# -1 = обрыв. Раньше код сравнивал ASCII с «1»/«2», поэтому меню из фрагментов
|
||||||
|
# не принимало ни одного нажатия — превращаем код в символ.
|
||||||
|
RAW=$(echo "$RESP" | sed -n 's/^200 result=\(-\{0,1\}[0-9]*\)$/\1/p')
|
||||||
|
if [ -z "$RAW" ] || [ "$RAW" -le 0 ]; then
|
||||||
DIGIT=""
|
DIGIT=""
|
||||||
else
|
else
|
||||||
|
DIGIT=$(printf "\\$(printf '%03o' "$RAW")")
|
||||||
ANY_DIGIT=1
|
ANY_DIGIT=1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
@ -80,6 +90,10 @@ getvar PICKUP_DAY1; PICKUP_DAY1=$VAR
|
||||||
getvar PICKUP_DAY2; PICKUP_DAY2=$VAR
|
getvar PICKUP_DAY2; PICKUP_DAY2=$VAR
|
||||||
getvar PICKUP_DAY3; PICKUP_DAY3=$VAR
|
getvar PICKUP_DAY3; PICKUP_DAY3=$VAR
|
||||||
getvar PICKUP_DAY1_FIRST; PICKUP_DAY1_FIRST=$VAR
|
getvar PICKUP_DAY1_FIRST; PICKUP_DAY1_FIRST=$VAR
|
||||||
|
# Даты доставки (планировщик берёт рабочие дни графика доставки из админки:
|
||||||
|
# не в день готовности и без выходных — готов в пятницу → понедельник и вторник)
|
||||||
|
getvar DELIVERY_DAY1; DELIVERY_DAY1=$VAR
|
||||||
|
getvar DELIVERY_DAY2; DELIVERY_DAY2=$VAR
|
||||||
CHOSEN_DATE=""
|
CHOSEN_DATE=""
|
||||||
|
|
||||||
# Пишем состояние ПОСЛЕ КАЖДОГО шага: если клиент положит трубку,
|
# Пишем состояние ПОСЛЕ КАЖДОГО шага: если клиент положит трубку,
|
||||||
|
|
@ -223,6 +237,7 @@ if [ "$TYPE" = "pkp" ] && [ -n "$PICKUP_DAY1" ]; then
|
||||||
CHOSEN_DAY=$DIGIT
|
CHOSEN_DAY=$DIGIT
|
||||||
case "$CHOSEN_DAY" in
|
case "$CHOSEN_DAY" in
|
||||||
9) play "ru/ss_optout"; OUTCOME=opt_out; break ;;
|
9) play "ru/ss_optout"; OUTCOME=opt_out; break ;;
|
||||||
|
0) play "ru/ss_operator"; OUTCOME=operator; break ;;
|
||||||
1|2|3) [ "$CHOSEN_DAY" -le "$DAYS" ] || CHOSEN_DAY="" ;;
|
1|2|3) [ "$CHOSEN_DAY" -le "$DAYS" ] || CHOSEN_DAY="" ;;
|
||||||
*) CHOSEN_DAY="" ;;
|
*) CHOSEN_DAY="" ;;
|
||||||
esac
|
esac
|
||||||
|
|
@ -235,6 +250,7 @@ if [ "$TYPE" = "pkp" ] && [ -n "$PICKUP_DAY1" ]; then
|
||||||
fi
|
fi
|
||||||
CHOSEN_DAY=$DIGIT
|
CHOSEN_DAY=$DIGIT
|
||||||
case "$CHOSEN_DAY" in
|
case "$CHOSEN_DAY" in
|
||||||
|
0) play "ru/ss_operator"; OUTCOME=operator; break ;;
|
||||||
1|2|3) [ "$CHOSEN_DAY" -le "$DAYS" ] || CHOSEN_DAY="" ;;
|
1|2|3) [ "$CHOSEN_DAY" -le "$DAYS" ] || CHOSEN_DAY="" ;;
|
||||||
*) CHOSEN_DAY="" ;;
|
*) CHOSEN_DAY="" ;;
|
||||||
esac
|
esac
|
||||||
|
|
@ -277,6 +293,83 @@ if [ "$TYPE" = "pkp" ] && [ -n "$PICKUP_DAY1" ]; then
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ─── Доставка: даты приходят из call-файла (DELIVERY_DAY1..2) ────────
|
||||||
|
# Доставляем только рабочими днями из «Графика работы» (админка) и не в день
|
||||||
|
# готовности: готов в пятницу → предлагаем понедельник и вторник, а не субботу
|
||||||
|
# с воскресеньем. Робот называет реальные дни, окно считает планировщик.
|
||||||
|
if [ "$TYPE" = "dlv" ] && [ -n "$DELIVERY_DAY1" ]; then
|
||||||
|
play "ru/ss_dlv_intro"
|
||||||
|
DLV_DAYS=1
|
||||||
|
[ -n "$DELIVERY_DAY2" ] && DLV_DAYS=2
|
||||||
|
|
||||||
|
DLV_TODAY=$(date +%F)
|
||||||
|
DLV_TOMORROW=$(date -d "+1 day" +%F)
|
||||||
|
dlv_day_frag () {
|
||||||
|
case "$1" in
|
||||||
|
"$DLV_TODAY") echo "ru/ss_pkp_d_today" ;;
|
||||||
|
"$DLV_TOMORROW") echo "ru/ss_pkp_d_tomorrow" ;;
|
||||||
|
*) echo "ru/ss_pkp_d_$(date -d "$1" +%u)" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
if [ "$DLV_DAYS" = "1" ]; then
|
||||||
|
DLV_MENU="ru/ss_dlv_only&$(dlv_day_frag "$DELIVERY_DAY1")"
|
||||||
|
else
|
||||||
|
DLV_MENU="ru/ss_dlv_menu1&$(dlv_day_frag "$DELIVERY_DAY1")&ru/ss_dlv_menu2&$(dlv_day_frag "$DELIVERY_DAY2")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
while [ -z "$OUTCOME" ]; do
|
||||||
|
say "EXEC Playback \"$DLV_MENU\""
|
||||||
|
wait_digit 9000
|
||||||
|
CHOSEN_DAY=$DIGIT
|
||||||
|
case "$CHOSEN_DAY" in
|
||||||
|
9) play "ru/ss_optout"; OUTCOME=opt_out; break ;;
|
||||||
|
0) play "ru/ss_operator"; OUTCOME=operator; break ;;
|
||||||
|
1|2) [ "$CHOSEN_DAY" -le "$DLV_DAYS" ] || CHOSEN_DAY="" ;;
|
||||||
|
*) CHOSEN_DAY="" ;;
|
||||||
|
esac
|
||||||
|
if [ -z "$CHOSEN_DAY" ]; then
|
||||||
|
say "EXEC Playback \"$DLV_MENU\""
|
||||||
|
wait_digit 9000
|
||||||
|
CHOSEN_DAY=$DIGIT
|
||||||
|
case "$CHOSEN_DAY" in
|
||||||
|
0) play "ru/ss_operator"; OUTCOME=operator; break ;;
|
||||||
|
1|2) [ "$CHOSEN_DAY" -le "$DLV_DAYS" ] || CHOSEN_DAY="" ;;
|
||||||
|
*) CHOSEN_DAY="" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
if [ -z "$CHOSEN_DAY" ]; then
|
||||||
|
if [ -z "$ANY_DIGIT" ] && [ "$RING_SEC" -ge 20 ]; then
|
||||||
|
OUTCOME=no_answer; break
|
||||||
|
fi
|
||||||
|
play "ru/ss_no_input"; OUTCOME=no_input_day; break
|
||||||
|
fi
|
||||||
|
case "$CHOSEN_DAY" in
|
||||||
|
1) CHOSEN_DATE="$DELIVERY_DAY1" ;;
|
||||||
|
2) CHOSEN_DATE="$DELIVERY_DAY2" ;;
|
||||||
|
esac
|
||||||
|
DAY=$CHOSEN_DAY
|
||||||
|
save
|
||||||
|
# Время доставки: до или после середины дня (границу задаёт админка,
|
||||||
|
# робот говорит просто «первая/вторая половина дня»).
|
||||||
|
digit "ru/ss_half_dlv" 9000
|
||||||
|
HALF=$DIGIT
|
||||||
|
if [ "$HALF" != "1" ] && [ "$HALF" != "2" ]; then
|
||||||
|
digit "ru/ss_half_dlv" 9000
|
||||||
|
HALF=$DIGIT
|
||||||
|
fi
|
||||||
|
if [ "$HALF" != "1" ] && [ "$HALF" != "2" ]; then
|
||||||
|
if [ -z "$ANY_DIGIT" ] && [ "$RING_SEC" -ge 20 ]; then
|
||||||
|
OUTCOME=no_answer; break
|
||||||
|
fi
|
||||||
|
play "ru/ss_no_input"; OUTCOME=no_input_half; break
|
||||||
|
fi
|
||||||
|
[ "$HALF" = "1" ] && play "ru/ss_ok_first" || play "ru/ss_ok_second"
|
||||||
|
CONF_DAY=$CHOSEN_DAY; CONF_HALF=$HALF
|
||||||
|
OUTCOME=confirmed
|
||||||
|
play "ru/ss_tail_dlv"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
# ─── выбор дня и времени, с возможностью переделать ──────────────────
|
# ─── выбор дня и времени, с возможностью переделать ──────────────────
|
||||||
while [ -z "$OUTCOME" ]; do
|
while [ -z "$OUTCOME" ]; do
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -239,20 +239,23 @@ def get_exclusion_match(conn, phone):
|
||||||
def load_work_days(conn, field="pickup_days"):
|
def load_work_days(conn, field="pickup_days"):
|
||||||
"""Рабочие дни по графику компании (1=пн … 7=вс).
|
"""Рабочие дни по графику компании (1=пн … 7=вс).
|
||||||
|
|
||||||
График правит админ в «График работы» (business_schedule_settings).
|
График правит админ в «График работы» (business_schedule_settings):
|
||||||
Звонки обязаны предлагать только рабочие дни: в пятницу «послезавтра» —
|
`pickup_days` — дни склада, `delivery_days` — дни доставки. Звонки обязаны
|
||||||
это воскресенье, когда самовывоз не работает.
|
опираться именно на эти данные: в пятницу «послезавтра» — это воскресенье,
|
||||||
|
когда ни самовывоз, ни доставка не работают.
|
||||||
"""
|
"""
|
||||||
|
columns = {"pickup_days", "delivery_days"} # белый список: имя колонки идёт в SQL
|
||||||
|
column = field if field in columns else "pickup_days"
|
||||||
default = [1, 2, 3, 4, 5]
|
default = [1, 2, 3, 4, 5]
|
||||||
try:
|
try:
|
||||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||||
cur.execute("SELECT pickup_days FROM business_schedule_settings WHERE id = 1")
|
cur.execute(f"SELECT {column} AS days FROM business_schedule_settings WHERE id = 1")
|
||||||
row = cur.fetchone() or {}
|
row = cur.fetchone() or {}
|
||||||
raw = row.get("pickup_days") or default
|
raw = row.get("days") or default
|
||||||
days = sorted({int(str(d).strip()) for d in raw if str(d).strip().isdigit()})
|
days = sorted({int(str(d).strip()) for d in raw if str(d).strip().isdigit()})
|
||||||
return days or default
|
return days or default
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warning(f"Не удалось прочитать график работы, беру пн-пт: {e}")
|
log.warning(f"Не удалось прочитать график работы ({column}), беру пн-пт: {e}")
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -291,6 +294,22 @@ def load_work_hours(conn):
|
||||||
return dict(defaults)
|
return dict(defaults)
|
||||||
|
|
||||||
|
|
||||||
|
def work_day_window(start_date, work_days, count):
|
||||||
|
"""`count` ближайших рабочих дней начиная со `start_date` включительно.
|
||||||
|
|
||||||
|
Выходные и нерабочие дни не сокращают окно, а пропускаются.
|
||||||
|
"""
|
||||||
|
days = []
|
||||||
|
day = start_date
|
||||||
|
guard = 0
|
||||||
|
while len(days) < count and guard < 30:
|
||||||
|
if day.isoweekday() in work_days:
|
||||||
|
days.append(day)
|
||||||
|
day += timedelta(days=1)
|
||||||
|
guard += 1
|
||||||
|
return days
|
||||||
|
|
||||||
|
|
||||||
def pickup_window(sms_sent_at, work_days=None, now=None, cutoff_minutes=None):
|
def pickup_window(sms_sent_at, work_days=None, now=None, cutoff_minutes=None):
|
||||||
"""Даты, на которые предлагаем самовывоз.
|
"""Даты, на которые предлагаем самовывоз.
|
||||||
|
|
||||||
|
|
@ -313,15 +332,7 @@ def pickup_window(sms_sent_at, work_days=None, now=None, cutoff_minutes=None):
|
||||||
today = local_now.date()
|
today = local_now.date()
|
||||||
ready = (sms_sent_at or now).astimezone(tz).date()
|
ready = (sms_sent_at or now).astimezone(tz).date()
|
||||||
|
|
||||||
# День готовности + два следующих рабочих дня.
|
window = work_day_window(ready, work_days, 3)
|
||||||
window = []
|
|
||||||
day = ready
|
|
||||||
guard = 0
|
|
||||||
while len(window) < 3 and guard < 30:
|
|
||||||
if day.isoweekday() in work_days:
|
|
||||||
window.append(day)
|
|
||||||
day += timedelta(days=1)
|
|
||||||
guard += 1
|
|
||||||
|
|
||||||
first_day = today
|
first_day = today
|
||||||
# Поздно для сегодняшнего забора — окно начинается со следующего дня.
|
# Поздно для сегодняшнего забора — окно начинается со следующего дня.
|
||||||
|
|
@ -330,6 +341,26 @@ def pickup_window(sms_sent_at, work_days=None, now=None, cutoff_minutes=None):
|
||||||
return [d.isoformat() for d in window if d >= first_day]
|
return [d.isoformat() for d in window if d >= first_day]
|
||||||
|
|
||||||
|
|
||||||
|
def delivery_window(sms_sent_at, work_days=None, now=None):
|
||||||
|
"""Даты, на которые предлагаем доставку.
|
||||||
|
|
||||||
|
Правило владельца: доставляем **только рабочими днями** и никогда в день
|
||||||
|
готовности — нужно время собрать маршрут. Значит, окно — два ближайших
|
||||||
|
рабочих дня после готовности:
|
||||||
|
• готов в понедельник → вт, ср
|
||||||
|
• готов в пятницу → пн, вт (в субботу и воскресенье не доставляем)
|
||||||
|
• звонок в среду по заказу, готовому в понедельник → ср, чт
|
||||||
|
"""
|
||||||
|
now = now or datetime.now(timezone.utc)
|
||||||
|
work_days = work_days or [1, 2, 3, 4, 5]
|
||||||
|
tz = timezone(timedelta(hours=3))
|
||||||
|
today = now.astimezone(tz).date()
|
||||||
|
ready = (sms_sent_at or now).astimezone(tz).date()
|
||||||
|
|
||||||
|
start = max(today, ready + timedelta(days=1))
|
||||||
|
return [d.isoformat() for d in work_day_window(start, work_days, 2)]
|
||||||
|
|
||||||
|
|
||||||
def is_first_half_available(now=None, half_end_minutes=None):
|
def is_first_half_available(now=None, half_end_minutes=None):
|
||||||
"""До границы половины дня (настройка склада, по умолчанию 12:00)
|
"""До границы половины дня (настройка склада, по умолчанию 12:00)
|
||||||
первую половину дня ещё можно предлагать клиенту."""
|
первую половину дня ещё можно предлагать клиенту."""
|
||||||
|
|
@ -386,6 +417,15 @@ def build_call_file(group, settings, attempt_no, is_test_call=False):
|
||||||
# Окно самовывоза закрыто (заказ готов более двух дней назад).
|
# Окно самовывоза закрыто (заказ готов более двух дней назад).
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Доставка: только рабочие дни графика и не в день готовности. Суббота —
|
||||||
|
# выходной, поэтому «готов в пятницу» даёт понедельник и вторник.
|
||||||
|
delivery_days = []
|
||||||
|
if call_type == "dlv":
|
||||||
|
delivery_days = delivery_window(
|
||||||
|
group.get("sms_sent_at") or group.get("created_at"),
|
||||||
|
settings.get("delivery_work_days"),
|
||||||
|
)
|
||||||
|
|
||||||
lines = [
|
lines = [
|
||||||
f"Channel: PJSIP/{dial}@mmedia",
|
f"Channel: PJSIP/{dial}@mmedia",
|
||||||
f"CallerID: {CALLER_ID}",
|
f"CallerID: {CALLER_ID}",
|
||||||
|
|
@ -404,6 +444,8 @@ def build_call_file(group, settings, attempt_no, is_test_call=False):
|
||||||
f"Setvar: PICKUP_DAY2={pickup_days[1] if len(pickup_days) > 1 else ''}",
|
f"Setvar: PICKUP_DAY2={pickup_days[1] if len(pickup_days) > 1 else ''}",
|
||||||
f"Setvar: PICKUP_DAY3={pickup_days[2] if len(pickup_days) > 2 else ''}",
|
f"Setvar: PICKUP_DAY3={pickup_days[2] if len(pickup_days) > 2 else ''}",
|
||||||
f"Setvar: PICKUP_DAY1_FIRST={1 if (pickup_days and is_first_half_available(now=None, half_end_minutes=settings.get('work_hours', {}).get('pickup_half_end'))) else 0}",
|
f"Setvar: PICKUP_DAY1_FIRST={1 if (pickup_days and is_first_half_available(now=None, half_end_minutes=settings.get('work_hours', {}).get('pickup_half_end'))) else 0}",
|
||||||
|
f"Setvar: DELIVERY_DAY1={delivery_days[0] if len(delivery_days) > 0 else ''}",
|
||||||
|
f"Setvar: DELIVERY_DAY2={delivery_days[1] if len(delivery_days) > 1 else ''}",
|
||||||
f"Setvar: API_URL={API_URL}",
|
f"Setvar: API_URL={API_URL}",
|
||||||
f"Setvar: API_KEY={API_KEY}",
|
f"Setvar: API_KEY={API_KEY}",
|
||||||
f"Setvar: TEST_MODE={1 if test_mode else 0}",
|
f"Setvar: TEST_MODE={1 if test_mode else 0}",
|
||||||
|
|
@ -622,8 +664,10 @@ def main():
|
||||||
conn = get_db_conn()
|
conn = get_db_conn()
|
||||||
try:
|
try:
|
||||||
settings = load_settings(conn)
|
settings = load_settings(conn)
|
||||||
# Рабочие дни и часы для окна самовывоза (выходные/закрытие склада робот не предлагает)
|
# Рабочие дни и часы из «Графика работы» (админка): выходные и нерабочие
|
||||||
settings["pickup_work_days"] = load_work_days(conn)
|
# дни робот не предлагает ни по самовывозу, ни по доставке.
|
||||||
|
settings["pickup_work_days"] = load_work_days(conn, "pickup_days")
|
||||||
|
settings["delivery_work_days"] = load_work_days(conn, "delivery_days")
|
||||||
settings["work_hours"] = load_work_hours(conn)
|
settings["work_hours"] = load_work_hours(conn)
|
||||||
|
|
||||||
# Добор «не взял трубку»: заявки, по которым сеть так и не ответила
|
# Добор «не взял трубку»: заявки, по которым сеть так и не ответила
|
||||||
|
|
|
||||||
|
|
@ -212,6 +212,9 @@ Deno.serve(async (request) => {
|
||||||
await supabase.from("order_groups").update(update).eq("id", orderGroupId);
|
await supabase.from("order_groups").update(update).eq("id", orderGroupId);
|
||||||
}
|
}
|
||||||
// ── no_answer / no_input: решаем, эскалировать ли ──
|
// ── no_answer / no_input: решаем, эскалировать ли ──
|
||||||
|
// Пороги берём из настроек кампании (админка → Автодозвон): раньше здесь
|
||||||
|
// стояли константы 2 и 5, поэтому поля «Не нажал ×N → менеджер» и
|
||||||
|
// «Всего попыток» в интерфейсе ни на что не влияли.
|
||||||
else {
|
else {
|
||||||
const { count: noInputCount } = await supabase
|
const { count: noInputCount } = await supabase
|
||||||
.from("autocall_log")
|
.from("autocall_log")
|
||||||
|
|
@ -223,8 +226,14 @@ Deno.serve(async (request) => {
|
||||||
.select("id", { count: "exact", head: true })
|
.select("id", { count: "exact", head: true })
|
||||||
.eq("order_group_id", orderGroupId);
|
.eq("order_group_id", orderGroupId);
|
||||||
|
|
||||||
const noInputEscalate = 2; // из настроек, но пока константа
|
const { data: settings } = await supabase
|
||||||
const maxAttempts = 5; // из настроек, но пока константа
|
.from("sms_campaign_settings")
|
||||||
|
.select("no_input_escalate_after, max_attempts")
|
||||||
|
.eq("campaign_type", "autocall_slot")
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
const noInputEscalate = Number(settings?.no_input_escalate_after ?? 2);
|
||||||
|
const maxAttempts = Number(settings?.max_attempts ?? 5);
|
||||||
const escalate = (noInputCount ?? 0) >= noInputEscalate || (totalAttempts ?? 0) >= maxAttempts;
|
const escalate = (noInputCount ?? 0) >= noInputEscalate || (totalAttempts ?? 0) >= maxAttempts;
|
||||||
if (escalate) {
|
if (escalate) {
|
||||||
await supabase.from("order_groups").update({
|
await supabase.from("order_groups").update({
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue