Initial commit

This commit is contained in:
2026-04-30 18:38:38 +03:00
commit 8d71819caf
20 changed files with 2134 additions and 0 deletions

293
handlers/fallback.py Normal file
View File

@@ -0,0 +1,293 @@
from database.repository import (
find_instructions,
get_instruction_id_by_title,
find_tech_problems,
get_tech_solution,
get_tech_problem_by_name,
find_tech_solutions_by_name_contains,
set_tech_problem_progress,
get_tech_problem_progress,
clear_tech_problem_progress,
)
from services.instructions import (
send_pinpad_error,
send_terminal_instruction,
)
from keyboards.factory import back_to_main, build_keyboard, tech_feedback_keyboard
_DOOR_MAIN_LABELS = {"дверь", "двери"}
_DOOR_CATEGORY_LABELS = {
"Дверца накопителя",
"Входная дверь",
"Рекламная дверь",
"Дверь кассовой зоны",
}
_DOOR_ACCUMULATOR_QUERIES = ["дверца накопителя"]
_DOOR_ENTRANCE_QUERIES = ["входная дверь", "входной двери", "ключ от входной двери"]
_DOOR_ADS_QUERIES = ["рекламная дверь"]
_DOOR_KZ_QUERIES = ["дверь кз", "дверь кассовой зоны"]
_DOOR_ACCUMULATOR_REQUIRED = ["накопител"]
_DOOR_ENTRANCE_REQUIRED = ["входн"]
_DOOR_ADS_REQUIRED = ["рекламн"]
_DOOR_KZ_REQUIRED = ["кз", "кассов"]
async def handle_fallback(message):
text = (message.text or "").strip().lower()
if not text:
await message.answer(
"✍️ Введите код ошибки или опишите проблему.",
keyboard=back_to_main(),
)
return
if message.text in _DOOR_CATEGORY_LABELS:
if message.text == "Дверца накопителя":
await _send_door_submenu(
message,
_DOOR_ACCUMULATOR_QUERIES,
"Выберите проблему с дверцей накопителя:",
required_substrings=_DOOR_ACCUMULATOR_REQUIRED,
)
return
if message.text == "Входная дверь":
await _send_door_submenu(
message,
_DOOR_ENTRANCE_QUERIES,
"Выберите проблему с входной дверью:",
required_substrings=_DOOR_ENTRANCE_REQUIRED,
)
return
if message.text == "Рекламная дверь":
await _send_door_submenu(
message,
_DOOR_ADS_QUERIES,
"Выберите проблему с рекламной дверью:",
required_substrings=_DOOR_ADS_REQUIRED,
)
return
if message.text == "Дверь кассовой зоны":
await _send_door_submenu(
message,
_DOOR_KZ_QUERIES,
"Выберите проблему с дверью кассовой зоны:",
required_substrings=_DOOR_KZ_REQUIRED,
)
return
# tech problem selection by name (from list)
tech_choice = get_tech_problem_by_name(text)
if tech_choice:
problem_id, _problem_name, task_type = tech_choice
await _send_tech_solution(message, problem_id, task_type)
return
# door уточнение
if text in _DOOR_MAIN_LABELS or any(part.startswith("двер") for part in text.split()):
keyboard = build_keyboard(
[{"title": t} for t in _DOOR_CATEGORY_LABELS],
back=True,
)
await message.answer(
"🚪 Уточните, какая дверь:",
keyboard=keyboard,
)
return
# tech problem feedback (text fallback)
if text in ("да", "нет"):
progress = get_tech_problem_progress(message.from_id)
if progress:
await _handle_tech_feedback(
user_id=message.from_id,
is_yes=(text == "да"),
send_fn=lambda msg: message.answer(msg, keyboard=back_to_main()),
)
return
# terminal instruction selection by title
instruction_id = get_instruction_id_by_title(text)
if instruction_id:
await send_terminal_instruction(
message,
instruction_id,
keyboard=back_to_main(),
)
return
# 1⃣ PINPAD — быстрый путь
if text.isdigit():
handled = await send_pinpad_error(
message,
text,
keyboard=back_to_main(),
)
if handled:
return
# 2⃣ TERMINAL — поиск инструкций (приоритет для кассы/эвотор/чек)
found = find_instructions(text)
if not found:
# fall through to tech problems
found = []
if len(found) == 1:
instruction_id, _ = found[0]
await send_terminal_instruction(
message,
instruction_id,
keyboard=back_to_main(),
)
return
if len(found) > 1:
buttons = [
{"title": title, "instruction_id": iid}
for iid, title in found
]
keyboard = build_keyboard(buttons, back=True)
await message.answer(
"🔎 Найдено несколько вариантов. Выберите нужный:",
keyboard=keyboard,
)
return
# 3⃣ TECH PROBLEMS — поиск по тех. проблемам
# (дойти сюда можно только если терминал ничего не нашёл)
tech_found = find_tech_problems(text)
if tech_found:
if len(tech_found) > 1:
buttons = []
for pid, _task_type in tech_found[:6]:
solution = get_tech_solution(pid)
if solution:
buttons.append({"title": solution[1]})
if buttons:
keyboard = build_keyboard(buttons, back=True)
await message.answer(
"🔎 Найдено несколько тех. проблем. Выберите нужную:",
keyboard=keyboard,
)
return
problem_id, task_type = tech_found[0]
await _send_tech_solution(message, problem_id, task_type)
return
await message.answer(
"Не удалось найти подходящую инструкцию.\n"
"✍️ Попробуйте изменить формулировку.",
keyboard=back_to_main(),
)
async def _send_tech_solution(message, problem_id: str, task_type: str):
solution = get_tech_solution(problem_id)
if not solution:
await message.answer(
("📝 Заполните форму:\nhttps://example.com/admin-form"
if task_type == "ADMIN"
else "📝 Заполните форму:\nhttps://example.com/tech-form"),
keyboard=back_to_main(),
)
return
(
_pid, problem_name, task_type, can_fix_self,
need_result_feedback, solution_steps, tools_needed,
when_stop_and_report,
) = solution
if can_fix_self == "NO":
await message.answer(
f"⚠️ {problem_name}\n"
"Самостоятельно не исправляется.\n"
+ ("📝 Форма:\nhttps://example.com/admin-form"
if task_type == "ADMIN"
else "📝 Форма:\nhttps://example.com/tech-form"),
keyboard=back_to_main(),
)
return
msg = f"🛠 {problem_name}\n\n✅ Шаги:\n{solution_steps}"
if tools_needed and tools_needed not in ("", "-"):
msg += f"\n\n🧰 Инструменты:\n{tools_needed}"
if when_stop_and_report:
msg += f"\n\n⛔ Когда остановиться и сообщить:\n{when_stop_and_report}"
await message.answer(msg, keyboard=back_to_main())
if need_result_feedback == "YES":
set_tech_problem_progress(message.from_id, problem_id, task_type, need_result_feedback)
await message.answer("❓ Помогло?", keyboard=tech_feedback_keyboard())
def _name_has_required(name: str, required_substrings):
if not required_substrings:
return True
name_l = (name or "").lower()
return any(sub in name_l for sub in required_substrings)
async def _send_door_submenu(message, queries, prompt: str, required_substrings=None):
uniq = {}
for q in queries:
# by solution name
for pid, name, task_type in find_tech_solutions_by_name_contains(q):
if _name_has_required(name, required_substrings):
uniq[pid] = (name, task_type)
# by keywords (TechProblems)
for pid, _task_type in find_tech_problems(q):
solution = get_tech_solution(pid)
if solution:
if _name_has_required(solution[1], required_substrings):
uniq[pid] = (solution[1], solution[2])
if not uniq:
await message.answer(
"Не удалось найти варианты по этой категории.",
keyboard=back_to_main(),
)
return
if len(uniq) == 1:
pid, (name, task_type) = next(iter(uniq.items()))
await _send_tech_solution(message, pid, task_type)
return
buttons = [{"title": name} for name, _t in list(uniq.values())[:6]]
keyboard = build_keyboard(buttons, back=True)
await message.answer(prompt, keyboard=keyboard)
async def _handle_tech_feedback(user_id: int, is_yes: bool, send_fn):
progress = get_tech_problem_progress(user_id)
if not progress:
return
_problem_id, task_type, _need_result_feedback = progress
clear_tech_problem_progress(user_id)
if is_yes:
await send_fn("✅ Отлично, рад помочь!")
return
form_url = "https://example.com/admin-form" if task_type == "ADMIN" else "https://example.com/tech-form"
await send_fn(f"📝 Заполните форму:\n{form_url}")
async def handle_tech_feedback_event(event) -> bool:
payload = event.get_payload_json() if hasattr(event, "get_payload_json") else None
if not payload or payload.get("tech_feedback") not in ("yes", "no"):
return False
await _handle_tech_feedback(
user_id=event.user_id,
is_yes=(payload.get("tech_feedback") == "yes"),
send_fn=lambda msg: event.send_message(msg, keyboard=back_to_main()),
)
return True