ERneSt⚡️os 6 months ago
kolomoyets #programming

Создание Telegram Бота для Управления Заявками с Интеграцией в CRM Систему

В этой статье мы рассмотрим, как создать Telegram бота, который интегрируется с системой управления заявками (например, CRM), используя Python, библиотеку aiogram для работы с Telegram API и aiomysql для асинхронного взаимодействия с MySQL базой данных. Весь смысл в том что логика бота такова что он будет считывать данные с БД и вытягивать заявки оттуда, а так же коментарии в них, есть всего две таблицы, в одной коменты во второй сами заявки. Сам же бот создает заявки от уже созданного пользователя в БД и ведет переписки тоже с него. Собственно говоря таких заявок может быть множество от одного пользователя, но для того что бы были разные телеграмм юзеры, и их заявки и переписки не переплитались пришлось 3 таблицу создать для хранения айди чата, и соотвественно подвязывать для каждого айди тг айди заявки

В современном мире автоматизация бизнес-процессов играет ключевую роль в управлении клиентскими запросами и оперативной работе с заявками. Telegram, как одна из самых популярных платформ для мессенджеров, предлагает широкие возможности для интеграции бизнес-процессов. В этой статье мы поговорим о том, как можно создать Telegram бота, который поможет автоматизировать процесс управления заявками в CRM системе, используя Python, библиотеку aiogram для работы с Telegram API и aiomysql для асинхронного взаимодействия с MySQL базой данных.


Основная Идея и Логика Работы Бота

Бот разработан для того, чтобы обеспечить эффективное взаимодействие между клиентами и системой управления заявками напрямую через Telegram. Он позволяет пользователям создавать новые заявки, а также получать обновления по уже существующим, включая комментарии от администрации.

Для реализации такого взаимодействия было создано три основные таблицы в базе данных:

  • Таблица rise_tickets для хранения информации о заявках.
  • Таблица rise_ticket_comments для комментариев по каждой заявке.
  • Таблица telegram_ticket_link для связи айди чата в Telegram с айди заявки, что позволяет избежать путаницы между заявками разных пользователей.

Техническая РеализацияПодключение к Базе Данных

Используя aiomysql, мы асинхронно подключаемся к базе данных. Это позволяет боту работать эффективно, не блокируя основной поток выполнения при обработке запросов к базе данных.


Так же, не забывай, подключение интеграция идет через БД а не АПИ


Создание Заявок

Пользователь может инициировать создание заявки, отправив команду /new_ticket. Бот запрашивает у пользователя название и описание заявки, после чего информация записывается в базу данных. Для каждой заявки присваивается уникальный идентификатор, который затем используется для общения с пользователем и обновления статуса заявки.


Обработка Комментариев

Бот периодически проверяет новые комментарии в базе данных и отправляет их соответствующим пользователям в Telegram. Это обеспечивает двустороннюю коммуникацию между администрацией и клиентом прямо в мессенджере.


Преимущества и Возможности

Такой подход позволяет:

  • Упростить процесс подачи заявок для клиентов.
  • Обеспечить оперативное информирование о статусе заявки.
  • Централизованно управлять всеми заявками и комментариями через один интерфейс.

Заключение

Интеграция Telegram бота с CRM системой — это эффективное решение для автоматизации управления заявками. Благодаря асинхронной работе с базой данных и возможности мгновенного общения с клиентами через популярный мессенджер, процесс обработки заявок становится проще и быстрее как для пользователей, так и для администрации.

Разработанный бот — это лишь один из примеров, как можно использовать современные технологии для оптимизации бизнес-процессов. Применение таких инновационных решений способствует повышению уровня клиентского сервиса и эффективности работы компании в целом.

Да, в заключение еще публикую код и к нему описание

# Конфигурация подключения к базе данных
db_config = {
    'host': 'localhost',
    'port': 3306,
    'user': 'root',
    'password': 'Erik456_debul.ua21',
    'db': 'crm',
    #'init_command': 'SET @@session.time_zone="02:00"',
}

from datetime import datetime, timedelta

class TicketForm(StatesGroup):
    title = State()
    description = State()

# Функция для добавления новой заявки в базу данных
async def insert_new_ticket(telegram_user_id, title, description):
    client_id = 3
    project_id = 0
    ticket_type_id = 1
    created_by = 5  # ID пользователя, от имени которого создаются тикеты
    requested_by = 5
    status = 'new'
    assigned_to = 1
    creator_name = 'Ernest Worker'
    creator_email = 'tawerkarespro18@gmail.com'
    labels = ''
    task_id = 0
    closed_at = '9999-12-31 23:59:59'
    merged_with_ticket_id = 0
    deleted = 0

    async with aiomysql.connect(**db_config, charset='utf8mb4', autocommit=True) as conn:
        async with conn.cursor() as cur:
            await cur.execute("""
                INSERT INTO rise_tickets 
                (client_id, project_id, ticket_type_id, title, created_by, requested_by, created_at, 
                 status, last_activity_at, assigned_to, creator_name, creator_email, labels, 
                 task_id, closed_at, merged_with_ticket_id, deleted) 
                VALUES (%s, %s, %s, %s, %s, %s, NOW(), %s, NOW(), %s, %s, %s, %s, %s, %s, %s, %s)
                """, (client_id, project_id, ticket_type_id, title, created_by, requested_by,
                      status, assigned_to, creator_name, creator_email, labels, task_id,
                      closed_at, merged_with_ticket_id, deleted))
            ticket_id = cur.lastrowid  # Получаем ID созданного тикета

            # Добавляем запись в telegram_ticket_link
            await cur.execute("""
                INSERT INTO telegram_ticket_link (telegram_user_id, ticket_id) 
                VALUES (%s, %s)
                """, (telegram_user_id, ticket_id))
            logging.info(f"Тикет {ticket_id} успешно создан и связан с пользователем Telegram {telegram_user_id}.")


# Обработчики команд и сообщений бота
@dp.message_handler(commands=['new_ticket'])
async def new_ticket(message: types.Message):
    await TicketForm.title.set()
    await message.answer("Введите название тикета:")

@dp.message_handler(state=TicketForm.title)
async def process_title(message: types.Message, state: FSMContext):
    async with state.proxy() as data:
        data['title'] = message.text
    await TicketForm.next()
    await message.answer("Введите описание тикета:")

@dp.message_handler(state=TicketForm.description)
async def process_description(message: types.Message, state: FSMContext):
    async with state.proxy() as data:
        data['description'] = message.text
    # Тут нужно получить telegram_user_id, например, так:
    telegram_user_id = message.from_user.id
    await insert_new_ticket(telegram_user_id, data['title'], data['description'])
    await state.finish()
    await message.answer("Ваш тикет успешно создан и добавлен в базу данных!")




# Идентификатор последнего отправленного комментария для каждой заявки
last_sent_comment_id = {}

async def fetch_new_comments():
    logging.info("Проверка новых комментариев от админа...")
    async with aiomysql.connect(**db_config) as conn:
        async with conn.cursor(aiomysql.DictCursor) as cur:
            await cur.execute("""
                SELECT rtc.id, rtc.description, rtc.ticket_id, ttl.telegram_user_id
                FROM rise_ticket_comments AS rtc
                JOIN rise_tickets AS rt ON rtc.ticket_id = rt.id
                JOIN telegram_ticket_link AS ttl ON rt.id = ttl.ticket_id
                WHERE rtc.created_by = 1 AND rtc.deleted = 0 AND rt.status != 'closed'
                ORDER BY rtc.id DESC
            """)
            comments = await cur.fetchall()
            for comment in comments:
                ticket_id = comment['ticket_id']
                if ticket_id not in last_sent_comment_id or comment['id'] > last_sent_comment_id[ticket_id]:
                    chat_id = comment['telegram_user_id']
                    if chat_id:
                        message = comment['description'].replace('<p>', '').replace('</p>', '\n').replace('<br>', '\n').strip()
                        await bot.send_message(chat_id, f"Новый комментарий к заявке '{ticket_id}': {message}")
                        last_sent_comment_id[ticket_id] = comment['id']
                    else:
                        logging.warning(f"Chat ID для пользователя с Telegram ID {comment['telegram_user_id']} не найден.")




@dp.message_handler(commands=['add_comment'])
async def add_comment(message: types.Message):
    args = message.get_args().split(maxsplit=1)
    if len(args) < 2:
        await message.reply("Используйте команду в формате: /add_comment <id_заявки> <комментарий>")
        return

    ticket_id, comment_text = args[0], args[1]
    try:
        ticket_id = int(ticket_id)  # Убедитесь, что ticket_id является числом
    except ValueError:
        await message.reply("ID заявки должен быть числом.")
        return

    sql_query = """
        INSERT INTO rise_ticket_comments (created_by, ticket_id, description, created_at, deleted, files, is_note)
        VALUES (%s, %s, %s, NOW(), 0, 'a:0:{}', 0)
    """
    sql_data = (5, ticket_id, comment_text)

    try:
        async with aiomysql.connect(**db_config, cursorclass=aiomysql.DictCursor) as conn:
            async with conn.cursor() as cur:
                await cur.execute("SELECT status FROM rise_tickets WHERE id = %s", (ticket_id,))
                ticket = await cur.fetchone()
                if ticket and ticket['status'] != 'closed':
                    await cur.execute(sql_query, sql_data)
                    await conn.commit()  # Явное подтверждение транзакции
                    await message.reply("Ваш комментарий добавлен к заявке.")
                else:
                    await message.reply("Заявка закрыта или не найдена. Добавление комментария невозможно.")
    except Exception as e:
        logging.error(f"Ошибка при выполнении SQL-запроса: {e}")
        await message.reply("Произошла ошибка при попытке добавить комментарий. Пожалуйста, попробуйте позже.")








notified_closed_tickets = set()
async def check_ticket_closed():
    logging.info("Проверка статуса заявок...")
    async with aiomysql.connect(**db_config) as conn:
        async with conn.cursor(aiomysql.DictCursor) as cur:
            await cur.execute("""
                SELECT rt.id, ttl.telegram_user_id
                FROM rise_tickets rt
                JOIN telegram_ticket_link ttl ON rt.id = ttl.ticket_id
                WHERE rt.status = 'closed'
            """)
            closed_tickets = await cur.fetchall()
            for ticket in closed_tickets:
                ticket_id = ticket['id']
                if ticket_id not in notified_closed_tickets:
                    telegram_user_id = ticket['telegram_user_id']
                    # Отправляем уведомление пользователю Telegram о закрытии тикета
                    await bot.send_message(telegram_user_id, f"Заявка {ticket_id} была закрыта.")
                    logging.info(f"Пользователь {telegram_user_id} уведомлен о закрытии заявки {ticket_id}.")
                    # Добавляем идентификатор заявки в множество уведомленных
                    notified_closed_tickets.add(ticket_id)


async def periodic_task(interval, task_func, *args):
    while True:
        try:
            await task_func(*args)  # Выполнение вашей функции
        except Exception as e:
            logging.error(f"Ошибка при выполнении задачи {task_func.__name__}: {e}")
        await asyncio.sleep(interval)  # Ожидание до следующего выполнения


async def on_startup(dp):
    await bot.send_message(ADMIN_ID, "Bot has been started")
    # Запуск периодической задачи проверки новых комментариев каждые 60 секунд
    asyncio.create_task(periodic_task(10, fetch_new_comments))
    # Запуск периодической задачи проверки закрытых заявок каждые 60 секунд
    asyncio.create_task(periodic_task(10, check_ticket_closed))



if __name__ == '__main__':
    from aiogram import executor
    executor.start_polling(dp, on_startup=on_startup)


Конфигурация базы данных

  • db_config: устанавливает параметры подключения к базе данных MySQL, где хранятся данные системы продажи билетов.


Состояния взаимодействия с пользователем

  • TicketForm: Использует айограммы StatesGroupдля управления взаимодействием с пользователями в форме формы. Штаты titleи descriptionиспользуются для последовательного сбора названия и описания билета.


Асинхронная функция для вставки новых билетов

  • insert_new_ticket: Асинхронно вставляет новый билет в базу данных, когда пользователь отправляет информацию через бота. Он использует aiomysqlбиблиотека для выполнения SQL INSERTкоманду и автоматически зафиксируйте транзакцию. telegram_user_idсвязан с новым билетом, чтобы отслеживать, какой пользователь Telegram его создал.


Обработчики команд

  • new_ticket: вызывает запрос пользователя на ввод названия нового билета.
  • process_title: сохраняет введенное название и запрашивает описание билета.
  • process_description: Сохраняет описание и звонки. insert_new_ticketдля создания новой записи в базе данных.

Обработка комментариев

  • fetch_new_comments: Регулярно проверяет новые комментарии администраторов к заявкам и уведомляет соответствующего пользователя Telegram. Он сохраняет запись последнего отправленного комментария, чтобы избежать отправки дубликатов.


Дополнительный обработчик команд для комментариев

  • add_comment: позволяет пользователю добавлять комментарий к заявке с помощью команды в формате /add_comment <ticket_id> <comment>. Функция проверяет вводимые данные и вставляет комментарий в соответствующую таблицу базы данных.


Мониторинг статуса заявок

  • check_ticket_closed: Периодически проверяет заявки, помеченные как закрытые в базе данных, и информирует об этом соответствующего пользователя Telegram.


Периодические задачи

  • periodic_task: служебная функция, которая запускает заданную задачу через определенные промежутки времени.
  • on_startup: Планирует запуск периодических задач. fetch_new_commentsи check_ticket_closedкогда бот запускается.

Запуск бота

  • The if __name__ == '__main__'блок инициирует опрос бота, настройку обработчиков команд и запуск асинхронных задач, запланированных в on_startup.


Микросервисы прагматика: как построить большую систему с помощью пачки монолитов

Микросервисы прагматика: как построить большую систему с помощью пачки...

1706541092.jpg
ERneSt⚡️os
6 months ago

Технологічні картки

Опис

1706541092.jpg
ERneSt⚡️os
5 months ago

Обробка доставки на станції (що надійшла з КЦ

1706541092.jpg
ERneSt⚡️os
5 months ago
Как вообще этот ваш CI CD настроить

Как вообще этот ваш CI CD настроить

1706541092.jpg
ERneSt⚡️os
6 months ago

Швидкі операції з довідником номенклатури, груповий друк ТТК

1706541092.jpg
ERneSt⚡️os
5 months ago