|
|
from googleapiclient.discovery import build |
|
|
from google.oauth2.service_account import Credentials |
|
|
import asyncio |
|
|
from aiogram.types import WebAppInfo |
|
|
import logging |
|
|
from aiogram import Bot, Dispatcher, types |
|
|
from aiogram.utils import executor |
|
|
from aiogram.types import ChatType |
|
|
from aiogram.utils.exceptions import BadRequest |
|
|
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton |
|
|
from aiogram.utils.exceptions import MessageCantBeDeleted |
|
|
import gspread |
|
|
from datetime import datetime |
|
|
from google.oauth2 import service_account |
|
|
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton, WebAppInfo, ReplyKeyboardMarkup, KeyboardButton |
|
|
import sentry_sdk |
|
|
|
|
|
|
|
|
|
|
|
sentry_sdk.init( |
|
|
dsn="https://697d3f2b6cb54a78202eeae0cb8c1f65@sentry.pirogov.ai/15", |
|
|
# Set traces_sample_rate to 1.0 to capture 100% |
|
|
# of transactions for performance monitoring. |
|
|
traces_sample_rate=1.0, |
|
|
) |
|
|
|
|
|
|
|
|
# Настройки бота |
|
|
API_TOKEN = '7414018470:AAGYF77aCd1BQpRnf38ys05ijkYJ25sNBuU' |
|
|
ADMIN_ID = 766945900 # ID администратора, которому будет приходить уведомление |
|
|
SPREADSHEET_ID = '128R3An6fb2PFGlzYMKb4SvEaSYCWUHHFVvial7llGno' # ID вашей Google Таблицы |
|
|
RANGE_NAME = 'Лист1!A1:O' # Диапазон данных в таблице |
|
|
CHAT_IDS = [-1002306913175, -1002266505101] # Замените на ID чатов, где бот администратор |
|
|
|
|
|
# Логирование |
|
|
logging.basicConfig(level=logging.INFO) |
|
|
|
|
|
bot = Bot(token=API_TOKEN) |
|
|
dp = Dispatcher(bot) |
|
|
|
|
|
# Настройки для Google Sheets API |
|
|
SCOPES = ['https://www.googleapis.com/auth/spreadsheets'] |
|
|
SERVICE_ACCOUNT_FILE = 'botforclinic-436512-0c117dd103a8.json' |
|
|
|
|
|
credentials = Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES) |
|
|
service = build('sheets', 'v4', credentials=credentials) |
|
|
|
|
|
# Настройка API для работы с документами |
|
|
SCOPES = ['https://www.googleapis.com/auth/documents', 'https://www.googleapis.com/auth/drive'] |
|
|
SERVICE_ACCOUNT_FILE = 'botforclinic-436512-0c117dd103a8.json' |
|
|
|
|
|
credentials = service_account.Credentials.from_service_account_file( |
|
|
SERVICE_ACCOUNT_FILE, scopes=SCOPES) |
|
|
|
|
|
docs_service = build('docs', 'v1', credentials=credentials) |
|
|
drive_service = build('drive', 'v3', credentials=credentials) |
|
|
|
|
|
# Идентификатор Google Документа (шаблона трудового договора) |
|
|
TEMPLATE_DOC_ID = '1xi4YiPNBEDvODS0SaVb0wgKZ_b_OBkas' |
|
|
|
|
|
# Словарь для хранения сотрудников, которых уже обработали |
|
|
known_employees = set() |
|
|
|
|
|
def create_contract_google(employee_data): |
|
|
# Копирование шаблона |
|
|
file_metadata = { |
|
|
'name': f"ТД_{employee_data[0]}_{datetime.now().strftime('%Y%m%d%H%M%S')}", |
|
|
'mimeType': 'application/vnd.google-apps.document', |
|
|
'parents': ['1fyA5btnwCQC-juE40T-uLSVHVIB-I2Ny'] |
|
|
} |
|
|
copied_file = drive_service.files().copy( |
|
|
fileId=TEMPLATE_DOC_ID, body=file_metadata).execute() |
|
|
document_id = copied_file['id'] |
|
|
|
|
|
# Замены текста в шаблоне |
|
|
replacements = { |
|
|
"{{ФИО}}": employee_data[0], |
|
|
"{{Серия паспорта}}": employee_data[3], |
|
|
"{{Номер паспорта}}": employee_data[4], |
|
|
"{{Кем выдан}}": employee_data[5], |
|
|
"{{Дата выдачи}}": employee_data[6], |
|
|
"{{Адрес}}": employee_data[8], |
|
|
"{{Дата рождения}}": employee_data[12], |
|
|
"{{ИНН}}": employee_data[11], |
|
|
"{{СНИЛС}}": employee_data[10], |
|
|
} |
|
|
|
|
|
requests = [{"replaceAllText": { |
|
|
"containsText": {"text": key, "matchCase": True}, |
|
|
"replaceText": value |
|
|
}} for key, value in replacements.items()] |
|
|
|
|
|
docs_service.documents().batchUpdate( |
|
|
documentId=document_id, body={'requests': requests}).execute() |
|
|
|
|
|
return f"https://docs.google.com/document/d/{document_id}/edit" |
|
|
|
|
|
|
|
|
|
|
|
# Словарь с соответствием специальности и ссылки на чат |
|
|
specialty_chat_links = { |
|
|
"Врачи": "https://t.me/botclinic", |
|
|
"Медсёстры": "https://t.me/+DcZn0yBO0RFjZTY6", |
|
|
"Санитарки": "https://t.me/joinchat/{chat_id_orderlies}", |
|
|
"Администраторы": "https://t.me/joinchat/{chat_id_admins}", |
|
|
"Операторы КЦ": "https://t.me/joinchat/{chat_id_operators}", |
|
|
"АУП": "https://t.me/joinchat/{chat_id_aap}" |
|
|
} |
|
|
|
|
|
|
|
|
# Функция для отправки сообщения о новом сотруднике с клавиатурой для выбора специальности |
|
|
async def notify_admin_about_new_employee(entry): |
|
|
full_name = entry[0] if entry else "Без имени" |
|
|
new_employee_id = entry[13] if len(entry) > 13 else None |
|
|
|
|
|
if new_employee_id: |
|
|
# Отправляем уведомление админу о новом сотруднике с клавишами выбора специальности |
|
|
keyboard = create_employee_keyboard() |
|
|
|
|
|
await bot.send_message( |
|
|
ADMIN_ID, |
|
|
f"Зарегистрирован новый сотрудник: {full_name}\nTelegram ID сотрудника: {new_employee_id}", |
|
|
reply_markup=keyboard |
|
|
) |
|
|
else: |
|
|
logging.error("Не удалось найти Telegram ID сотрудника.") |
|
|
|
|
|
|
|
|
# Функция для проверки новых сотрудников |
|
|
async def check_for_new_entries(): |
|
|
sheet = service.spreadsheets() |
|
|
|
|
|
while True: |
|
|
try: |
|
|
result = sheet.values().get(spreadsheetId=SPREADSHEET_ID, range=RANGE_NAME).execute() |
|
|
values = result.get('values', []) |
|
|
except Exception as e: |
|
|
logging.error(f"Ошибка при чтении таблицы: {e}") |
|
|
await asyncio.sleep(30) # Ждем перед следующей попыткой |
|
|
continue |
|
|
|
|
|
# Проверяем каждую запись на наличие нового сотрудника |
|
|
for entry in values: |
|
|
full_name = entry[0] if entry else "Без имени" |
|
|
new_employee_id = entry[13] if len(entry) > 13 else None # Telegram ID сотрудника |
|
|
|
|
|
if full_name and full_name not in known_employees: |
|
|
known_employees.add(full_name) |
|
|
|
|
|
# Генерация трудового договора |
|
|
try: |
|
|
contract_url = create_contract_google(entry) |
|
|
|
|
|
# Уведомление для администратора |
|
|
await bot.send_message( |
|
|
ADMIN_ID, |
|
|
f"Зарегистрирован новый сотрудник: {full_name}\nТрудовой договор: {contract_url}", |
|
|
) |
|
|
|
|
|
# Уведомление для сотрудника |
|
|
if new_employee_id: |
|
|
welcome_message = f"Большое спасибо! Мы получили данные для заключения трудового договора с вами." |
|
|
await bot.send_message( |
|
|
new_employee_id, |
|
|
welcome_message, |
|
|
) |
|
|
|
|
|
# Оповещаем админа и отправляем клавиатуру для выбора специальности |
|
|
await notify_admin_about_new_employee(entry) |
|
|
|
|
|
except Exception as e: |
|
|
logging.error(f"Ошибка при создании трудового договора: {e}") |
|
|
await bot.send_message(ADMIN_ID, "Произошла ошибка при создании трудового договора.") |
|
|
|
|
|
await asyncio.sleep(10) # Проверяем таблицу каждые 10 секунд |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Функция отправки сообщения о прохождении инструктажа |
|
|
async def send_fire_safety_instructions(employee_id, full_name, employee_data): |
|
|
message_text = f""" |
|
|
{full_name}, добрый день. Вы приглашены на корпоративный канал Клиники в Telegram. В закрепленном сообщении на этом канале Вы сможете ознакомиться с такими разделами: |
|
|
|
|
|
👉[Наши сотрудники](https://t.me/c/1414064442/5) - актуальный список всех сотрудников Клиники; |
|
|
👉[Контакты сотрудников](https://t.me/c/1414064442/39) - номера телефонов сотрудников для скачивания в телефонную книгу; |
|
|
👉[История клиники](https://t.me/c/1414064442/6) |
|
|
👉[Структура рабочих чатов](https://t.me/c/1414064442/10) |
|
|
👉[Инструктаж по пожарной безопасности в Клинике ухо, горло, нос](https://disk.yandex.ru/i/i1k41Z2SznfpOA) |
|
|
👉[Обратная связь](https://forms.gle/Dfjrb8K1NzzjmW5j7) - оставить предложение, пожелание или задать вопрос руководству (анонимно). |
|
|
|
|
|
**ВАЖНО:** |
|
|
В разделе [Инструктаж по пожарной безопасности в Клинике ухо, горло, нос](https://disk.yandex.ru/i/i1k41Z2SznfpOA) Вам необходимо изучить материалы по Пожарной безопасности, уделить особое внимание теме - порядок действий и по каким номерам звонить в случае пожароопасной ситуации. |
|
|
|
|
|
После этого пройти тестирование по данной теме (21 вопрос - зачет 70% правильных ответов): |
|
|
👉[Тестирование по пожарной безопасности](https://forms.gle/VLEx2Gf1h8grpXXu5) |
|
|
|
|
|
**Срок обучения и прохождения тестирования** - до начала выполнения трудовой функции! |
|
|
|
|
|
Спасибо! |
|
|
|
|
|
С уважением, |
|
|
Жуланова Наталья Александровна, |
|
|
помощник генерального директора ООО "Клиника ухо, горло, нос им. проф. Е.Н.Оленевой" (г. Пермь) |
|
|
📞 +7 (902) 64-54-648 |
|
|
""" |
|
|
try: |
|
|
# Отправка сообщения сотруднику |
|
|
await bot.send_message(employee_id, message_text, parse_mode="Markdown") |
|
|
logging.info(f"Сообщение о пожарной безопасности отправлено {full_name}.") |
|
|
|
|
|
|
|
|
except Exception as e: |
|
|
logging.error(f"Ошибка при отправке инструктажа или документа: {e}") |
|
|
await bot.send_message(ADMIN_ID, "Произошла ошибка при отправке инструктажа или документа.") |
|
|
|
|
|
|
|
|
# Обработчик нажатия на кнопки выбора специальности |
|
|
|
|
|
@dp.message_handler(lambda message: message.text in specialty_chat_links.keys(), chat_type=types.ChatType.PRIVATE) |
|
|
async def handle_specialty_selection(message: types.Message): |
|
|
selected_specialty = message.text |
|
|
data = sheet.get_all_values() |
|
|
last_employee_row = len(data) |
|
|
|
|
|
if last_employee_row < 2: |
|
|
await message.answer("В таблице пока нет сотрудников для назначения специальности.") |
|
|
return |
|
|
|
|
|
new_employee_id = data[last_employee_row - 1][13] |
|
|
full_name = data[last_employee_row - 1][0] |
|
|
|
|
|
if not new_employee_id: |
|
|
await message.answer("Не удалось найти Telegram ID сотрудника в таблице.") |
|
|
return |
|
|
|
|
|
try: |
|
|
sheet.update_cell(last_employee_row, 16, selected_specialty) |
|
|
await message.answer(f"Специальность сотрудника обновлена на: {selected_specialty}") |
|
|
|
|
|
chat_invite_link = specialty_chat_links.get(selected_specialty) |
|
|
if chat_invite_link: |
|
|
await bot.send_message( |
|
|
new_employee_id, |
|
|
f"Поздравляем! Вы были добавлены в нашу команду как {selected_specialty}. Вот ссылка на вашу беседу: {chat_invite_link}" |
|
|
) |
|
|
await message.answer(f"Ссылка на беседу отправлена сотруднику.") |
|
|
|
|
|
# Отправка сообщения о прохождении инструктажа |
|
|
await send_fire_safety_instructions(new_employee_id, full_name, data[last_employee_row - 1]) |
|
|
|
|
|
except Exception as e: |
|
|
await message.answer(f"Ошибка при обновлении таблицы: {e}") |
|
|
|
|
|
|
|
|
|
|
|
# Функция для генерации клавиатуры с кнопками типа сотрудника |
|
|
def create_employee_keyboard(): |
|
|
keyboard = ReplyKeyboardMarkup(resize_keyboard=True, one_time_keyboard=False) |
|
|
button_doctors = KeyboardButton("Врачи") |
|
|
button_nurses = KeyboardButton("Медсёстры") |
|
|
button_orderlies = KeyboardButton("Санитарки") |
|
|
button_admins = KeyboardButton("Администраторы") |
|
|
button_operators = KeyboardButton("Операторы КЦ") |
|
|
button_aap = KeyboardButton("АУП") |
|
|
|
|
|
keyboard.add(button_doctors, button_nurses) |
|
|
keyboard.add(button_orderlies, button_admins) |
|
|
keyboard.add(button_operators, button_aap) |
|
|
|
|
|
return keyboard |
|
|
|
|
|
|
|
|
# Обработчик команды /start |
|
|
@dp.message_handler(commands=['start'], chat_type=types.ChatType.PRIVATE) |
|
|
async def send_welcome(message: types.Message): |
|
|
user_id = message.from_user.id |
|
|
|
|
|
# Отправка фото |
|
|
photo_path = r'D:\Users\Maxim\PycharmProjectsbot\Bot_for_clinic\start_foto.png' |
|
|
await bot.send_photo(user_id, types.InputFile(photo_path)) |
|
|
|
|
|
# Отправка текста с кнопкой |
|
|
await bot.send_message( |
|
|
user_id, |
|
|
"Добрый день, этот бот нужен Вам для начала прохождения процедуры трудоустройства в " |
|
|
"Общество с ограниченной ответственностью «Клиника ухо, горло, нос имени профессора Е.Н. Оленевой».\n" |
|
|
"Нажмите, пожалуйста, на команду 👉 /registration", |
|
|
reply_markup=get_registration_button() |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Обработчик команды регистрации |
|
|
@dp.message_handler(commands=['registration'], chat_type=types.ChatType.PRIVATE) |
|
|
async def send_welcome(message: types.Message): |
|
|
await message.answer( |
|
|
"▪️В соответствии с требованиями статьи 9 Федерального закона от 27.07.2006 г. «О персональных данных» № 152-ФЗ даю согласие на обработку своих персональных данных.\n" |
|
|
"▪️Наименование и адрес оператора, получающего согласие субъекта персональных данных: Общество с ограниченной ответственностью «Клиника ухо, горло, нос имени профессора Е.Н. Оленевой».\n" |
|
|
"▪️Цель обработки персональных данных: организация труда и осуществление иных, связанных с этим мероприятий.", |
|
|
reply_markup=get_consent_keyboard() |
|
|
) |
|
|
|
|
|
# Обработчик ответа пользователя |
|
|
@dp.message_handler(lambda message: message.text in ["✅ Даю согласие на обработку своих персональных данных", "❌ Не даю согласие на обработку своих персональных данных"]) |
|
|
async def process_consent(message: types.Message): |
|
|
user_id = message.from_user.id |
|
|
|
|
|
if message.text == "✅ Даю согласие на обработку своих персональных данных": |
|
|
form_url = f"https://tgbotkugn.pirogov.ai/?user_id={user_id}" |
|
|
form_keyboard = InlineKeyboardMarkup().add( |
|
|
InlineKeyboardButton( |
|
|
text="Ввести персональные данные", web_app=WebAppInfo(url=form_url) |
|
|
) |
|
|
) |
|
|
await message.answer( |
|
|
"Спасибо за согласие! Нажмите кнопку ниже, чтобы ввести персональные данные:", |
|
|
reply_markup=form_keyboard |
|
|
) |
|
|
else: |
|
|
await message.answer( |
|
|
"Вы не дали согласие на обработку персональных данных.\n" |
|
|
"Вы не можете продолжить регистрацию.", |
|
|
reply_markup=get_consent_keyboard() |
|
|
) |
|
|
|
|
|
@dp.message_handler(commands=['getchatid'], chat_type=[ChatType.GROUP, ChatType.SUPERGROUP]) |
|
|
async def get_chat_id(message: types.Message): |
|
|
# Проверяем статус пользователя в чате |
|
|
chat_member = await bot.get_chat_member(message.chat.id, message.from_user.id) |
|
|
if chat_member.status not in ['administrator', 'creator']: |
|
|
# Отправляем предупреждение о недостатке прав |
|
|
warning_message = await message.answer("У вас нет прав на выполнение этой команды.") |
|
|
|
|
|
# Удаляем команду /getchatid через 1 секунду |
|
|
await asyncio.sleep(1) |
|
|
try: |
|
|
await message.delete() |
|
|
except MessageCantBeDeleted: |
|
|
logging.warning("Не удалось удалить сообщение с командой /getchatid") |
|
|
|
|
|
# Удаляем предупреждение через 5 секунд |
|
|
await asyncio.sleep(5) |
|
|
try: |
|
|
await warning_message.delete() |
|
|
except MessageCantBeDeleted: |
|
|
logging.warning("Не удалось удалить сообщение с предупреждением") |
|
|
return |
|
|
|
|
|
# Отправляем ID и название чата администратору бота |
|
|
chat_id = message.chat.id |
|
|
chat_title = message.chat.title or "Без названия" |
|
|
await bot.send_message(ADMIN_ID, f"ID чата: {chat_id}\nНазвание чата: {chat_title}") |
|
|
|
|
|
# Удаляем команду /getchatid через 1 секунду |
|
|
await asyncio.sleep(1) |
|
|
try: |
|
|
await message.delete() |
|
|
except MessageCantBeDeleted: |
|
|
logging.warning("Не удалось удалить сообщение с командой /getchatid") |
|
|
|
|
|
|
|
|
|
|
|
# Инициализация доступа к Google Sheets |
|
|
gc = gspread.service_account(filename=SERVICE_ACCOUNT_FILE) |
|
|
sheet = gc.open_by_key(SPREADSHEET_ID).sheet1 # Открываем первую таблицу |
|
|
|
|
|
|
|
|
@dp.message_handler(commands=['kick'], chat_type=types.ChatType.PRIVATE) |
|
|
async def kick_user(message: types.Message): |
|
|
if message.from_user.id != ADMIN_ID: |
|
|
await message.answer("Эта команда доступна только администратору бота.") |
|
|
return |
|
|
|
|
|
# Получаем ФИО из сообщения |
|
|
full_name = message.get_args().strip() |
|
|
if not full_name: |
|
|
await message.answer("Пожалуйста, укажите ФИО пользователя для кика.") |
|
|
return |
|
|
|
|
|
# Поиск ID пользователя по ФИО в Google Таблице |
|
|
try: |
|
|
data = sheet.get_all_values() # Получаем все значения в таблице |
|
|
except Exception as e: |
|
|
await message.answer("Не удалось получить данные из таблицы.") |
|
|
return |
|
|
|
|
|
user_id = None |
|
|
for row in data: |
|
|
if row and row[0] == full_name: # ФИО в первом столбце |
|
|
user_id = row[13] # ID в 14-м столбце (индекс 13) |
|
|
break |
|
|
|
|
|
if user_id is None: |
|
|
await message.answer(f"Пользователь с ФИО '{full_name}' не найден в таблице.") |
|
|
return |
|
|
|
|
|
errors = [] # Список для хранения ошибок |
|
|
for chat_id in CHAT_IDS: |
|
|
try: |
|
|
await bot.kick_chat_member(chat_id, user_id) |
|
|
except BadRequest as e: |
|
|
errors.append(f"Ошибка в чате {chat_id}: {e}") |
|
|
|
|
|
# Отправляем сообщение админу один раз |
|
|
if errors: |
|
|
await bot.send_message( |
|
|
ADMIN_ID, |
|
|
f"Пользователь с ID {user_id} был исключён из чатов, " |
|
|
f"но произошли ошибки: {'; '.join(errors)}" |
|
|
) |
|
|
else: |
|
|
await bot.send_message(ADMIN_ID, f"Пользователь с ID {user_id} был исключён из всех чатов.") |
|
|
|
|
|
await message.answer(f"Команда на кик пользователя с ФИО '{full_name}' отправлена во все чаты.") |
|
|
|
|
|
|
|
|
def get_registration_button(): |
|
|
keyboard = ReplyKeyboardMarkup(resize_keyboard=True) |
|
|
registration_button = KeyboardButton("/registration") |
|
|
keyboard.add(registration_button) |
|
|
return keyboard |
|
|
|
|
|
|
|
|
def get_consent_keyboard(): |
|
|
keyboard = ReplyKeyboardMarkup(resize_keyboard=True, one_time_keyboard=True) |
|
|
consent_button = KeyboardButton("✅ Даю согласие на обработку своих персональных данных") |
|
|
deny_button = KeyboardButton("❌ Не даю согласие на обработку своих персональных данных") |
|
|
keyboard.add(consent_button, deny_button) |
|
|
return keyboard |
|
|
|
|
|
if __name__ == '__main__': |
|
|
# Запускаем отслеживание таблицы и бота |
|
|
loop = asyncio.get_event_loop() |
|
|
loop.create_task(check_for_new_entries()) |
|
|
executor.start_polling(dp, skip_updates=True)
|
|
|
|