# /opt/trueconf_bot/lk/info_service.py import re import logging import calendar import pymssql from datetime import datetime import config.config as config logger = logging.getLogger(__name__) def get_db_connection(): if not config.DB_USER or not config.DB_PASSWORD: raise Exception("Пароли БД не загружены из Passwork.") return pymssql.connect( server=config.SQL_SERVER, user=config.DB_USER, password=config.DB_PASSWORD, database=config.SQL_DB_NAME, charset='cp1251' ) def get_lk_profile_by_card_id(card_id: str) -> dict: """Запрос 1: Получение информации о подразделении, должности и стаже по CARD_ID""" try: current_date = datetime.now().strftime('%Y-%m-%d') conn = get_db_connection() cursor = conn.cursor() query = """ select f.SNAME [Компания], d.NAME [Отдел], appoint.NAME [Должность], rtrim(ltrim(e.STAGO)) [Страховой стаж], rtrim(ltrim(e.STAGP)) [Стаж на предприятии] from (select e.ID, e.FIRM_ID, st.STAGO, st.STAGP from UOV_SELFSERVICE_PR_EMP e join UOV_SELFSERVICE_PR_CARD c on c.ID=e.CARD_ID join UOV_SELFSERVICE_PR_STAG_CUR st on st.CARD_ID=c.ID where c.ID=%s group by e.ID , e.FIRM_ID, st.STAGO, st.STAGP) as e join UOV_SELFSERVICE_PR_TRANS t on t.EMP_ID = e.ID join UOV_SELFSERVICE_HR_FIRM f on f.ID = e.FIRM_ID join UOV_SELFSERVICE_HR_DEPT d on d.ID = t.DEPT_ID join UOV_SELFSERVICE_ST_APPOINT appoint on appoint.ID = t.APPOINT_ID where (t.D_FROM <= cast(%s as date) and (t.D_TO >= cast(%s as date) or t.D_TO = cast('1999-12-31' as date))) group by f.SNAME, d.NAME, appoint.NAME, rtrim(ltrim(e.STAGO)), rtrim(ltrim(e.STAGP)) """ cursor.execute(query, (card_id, current_date, current_date)) row = cursor.fetchone() conn.close() if not row: raise Exception(f"Запись сотрудника по CARD_ID {card_id} не найдена в БД.") return { 'company': str(row[0]).strip() if row[0] else "", 'dept': str(row[1]).strip() if row[1] else "", 'job': str(row[2]).strip() if row[2] else "", 'stag_o': str(row[3]).strip() if row[3] else "0", 'stag_p': str(row[4]).strip() if row[4] else "0" } except Exception as e: raise Exception(f"SQL Profile Error: {e}") def get_vacation_records(card_id: str) -> tuple: """Возвращает статический заголовок (баланс + планы) и список строк истории отпусков""" try: conn = get_db_connection() cursor = conn.cursor() cursor.execute("SELECT TOP 1 e.ID FROM UOV_SELFSERVICE_PR_EMP e WHERE e.CARD_ID = %s", (card_id,)) row = cursor.fetchone() if not row: conn.close() return "🌴 Информация об отпусках\n\nСотрудник не найден в базе данных.", [] emp_id = row[0] # Балансы q_balance = """ SELECT comp.LEAVE_NAME, comp.DAYS FROM UO_SELFSERVICE_LEAVE_COMP as comp INNER JOIN UOV_SELFSERVICE_PR_TRANS as trans ON trans.EMP_ID = comp.EMP_ID WHERE GETDATE() BETWEEN trans.D_FROM and trans.D_TO AND comp.EMP_ID = %s """ cursor.execute(q_balance, (emp_id,)) balances = cursor.fetchall() # Планы q_plans = """ SELECT e.D_FROM, e.DAYS FROM UOV_SELFSERVICE_PR_LEAVE_PLAN e WHERE e.EMP_ID = %s AND e.D_FROM >= CAST(GETDATE() AS DATE) ORDER BY e.D_FROM ASC """ cursor.execute(q_plans, (emp_id,)) plans = cursor.fetchall() # Архив всей истории q_history = """ SELECT e.D_FROM, e.D_TO, e.DAYS, CAST(e.NAME AS VARCHAR(250)), e.ADDITIONAL FROM UOV_SELFSERVICE_PR_LEAVE_ALL e WHERE e.EMP_ID = %s AND e.D_FROM < CAST(GETDATE() AS DATE) ORDER BY e.D_FROM DESC """ cursor.execute(q_history, (emp_id,)) history = cursor.fetchall() conn.close() # Формируем неизменяемую верхнюю часть (шапку) справки static_header = "🌴 Информация об отпусках\n\n🟢 Доступный остаток:\n" if balances: for b in balances: b_name = str(b[0]).strip() if b[0] else "Ежегодный оплачиваемый отпуск" static_header += f"• {int(b[1]) if b[1] else 0} дн. ({b_name})\n" else: static_header += "• 0 дн.\n" static_header += "\n⏳ Предстоящие отпуска:\n" if plans: for p in plans: d_from = p[0].strftime('%d.%m.%Y') if hasattr(p[0], 'strftime') else str(p[0])[:10] static_header += f"• {d_from}{int(p[1]) if p[1] else 0} дн.\n" else: static_header += "• Нет запланированных отпусков\n" # Нарезаем строки истории в плоский массив для пагинатора history_lines = [] if history: for h in history: h_from = h[0].strftime('%d.%m.%Y') if hasattr(h[0], 'strftime') else str(h[0])[:10] h_to = h[1].strftime('%d.%m.%Y') if hasattr(h[1], 'strftime') else str(h[1])[:10] h_days = int(h[2]) if h[2] else 0 if h[3] and str(h[3]).strip(): type_str = str(h[3]).strip() else: type_str = "Дополнительный" if h[4] == 1 else "Основной" history_lines.append(f"• {h_from} — {h_to} : 🔹 {h_days} дн. ({type_str})") return static_header, history_lines except Exception as e: raise Exception(f"SQL Vacation Records Error: {e}") def get_sick_leaves_text(card_id: str) -> str: """Запрашивает историю больничных листов строго по CARD_ID""" try: conn = get_db_connection() cursor = conn.cursor() cursor.execute("SELECT e.ID FROM UOV_SELFSERVICE_PR_EMP e WHERE e.CARD_ID = %s", (card_id,)) row = cursor.fetchone() if not row: conn.close() return "📑 Ваши больничные листы:\n\nСотрудник не найден в базе данных." emp_id = row[0] query = """ SELECT e.D_FROM, e.D_TO FROM UOV_SELFSERVICE_PR_ILL e WHERE e.EMP_ID = %s ORDER BY e.D_FROM DESC """ cursor.execute(query, (emp_id,)) results = cursor.fetchall() conn.close() if not results: return "📑 Ваши больничные листы:\n\nНичего не найдено." response = "📑 Ваши больничные листы:\n\n" for r in results: d_from = r[0].strftime('%d.%m.%Y') if hasattr(r[0], 'strftime') else str(r[0])[:10] d_to = r[1].strftime('%d.%m.%Y') if hasattr(r[1], 'strftime') else str(r[1])[:10] days_count = "" if hasattr(r[0], 'strftime') and hasattr(r[1], 'strftime'): delta_days = (r[1] - r[0]).days + 1 days_count = f" — {delta_days} дн." response += f"🩺 {d_from} по {d_to}{days_count}\n" return response except Exception as e: raise Exception(f"SQL Sick Leaves Error: {e}") def get_missions_records(card_id: str) -> list: """Возвращает структурированный список pre-formatted текстовых блоков командировок""" try: conn = get_db_connection() cursor = conn.cursor() query = """ SELECT mp.D_FROM, mp.D_TO, CAST(mp.NAME AS VARCHAR(250)), CAST(mp.CITY AS VARCHAR(250)) FROM UOV_SELFSERVICE_PR_EMP e JOIN UOV_SELFSERVICE_PR_MISSION m ON e.ID = m.EMP_ID JOIN UOV_SELFSERVICE_PR_MISSION_P mp ON mp.MISSION_ID = m.ID WHERE e.CARD_ID = %s ORDER BY mp.D_FROM DESC """ cursor.execute(query, (card_id,)) results = cursor.fetchall() conn.close() mission_blocks = [] for r in results: d_from = r[0].strftime('%d.%m.%Y') if hasattr(r[0], 'strftime') else str(r[0])[:10] d_to = r[1].strftime('%d.%m.%Y') if hasattr(r[1], 'strftime') else str(r[1])[:10] firm_name = str(r[2]).strip() if r[2] else "Организация не указана" city = str(r[3]).strip() if r[3] else "Город не указан" block = f"• 📅 {d_from}{d_to}\n 📍 г. {city}, {firm_name}" mission_blocks.append(block) return mission_blocks except Exception as e: raise Exception(f"SQL Missions Records Error: {e}") def get_transfers_records(card_id: str) -> list: """Возвращает структурированный список кадровых переходов сотрудника по его CARD_ID""" try: conn = get_db_connection() cursor = conn.cursor() query = """ SELECT tr.D_FROM, CASE WHEN tr.D_TO = CAST('1999-12-31' AS DATE) THEN CAST('2099-12-31' AS DATE) ELSE tr.D_TO END as D_TO, CAST(d.NAME AS VARCHAR(250)) as DEPT, CAST(a.NAME AS VARCHAR(250)) as APPOINT FROM UOV_SELFSERVICE_PR_EMP e JOIN UOV_SELFSERVICE_PR_TRANS tr ON tr.EMP_ID = e.ID JOIN UOV_SELFSERVICE_HR_DEPT d ON d.ID = tr.DEPT_ID JOIN UOV_SELFSERVICE_ST_APPOINT a ON a.ID = tr.APPOINT_ID WHERE e.CARD_ID = %s ORDER BY tr.D_FROM DESC """ cursor.execute(query, (card_id,)) results = cursor.fetchall() conn.close() transfer_blocks = [] for r in results: d_from = r[0].strftime('%d.%m.%Y') if hasattr(r[0], 'strftime') else str(r[0])[:10] if r[1] and (r[1].year == 2099 or r[1].year == 1999): d_to = "наст. время" else: d_to = r[1].strftime('%d.%m.%Y') if hasattr(r[1], 'strftime') else str(r[1])[:10] dept = str(r[2]).strip() if r[2] else "Не указан" appoint = str(r[3]).strip() if r[3] else "Не указана" block = f"• ⏳ {d_from} — {d_to}\n 💼 Должность: {appoint}\n 📂 Отдел: {dept}" transfer_blocks.append(block) return transfer_blocks except Exception as e: raise Exception(f"SQL Transfers Records Error: {e}") def get_rewards_text(card_id: str) -> str: """Запрашивает историю наград по CARD_ID""" try: conn = get_db_connection() cursor = conn.cursor() cursor.execute(""" SELECT e.D, CAST(e.NAME AS VARCHAR(1000)) FROM UOV_SELFSERVICE_PR_REW_ENC e WHERE e.CARD_ID = %s ORDER BY e.D """, (card_id,)) results = cursor.fetchall() conn.close() if not results: return "🏆 Ваши награды и поощрения:\n\nНичего не найдено." response = "🏆 Ваши награды и поощрения:\n\n" for r in results: date_str = r[0].strftime('%d.%m.%Y') if hasattr(r[0], 'strftime') else str(r[0])[:10] response += f"🏅 {date_str} — {str(r[1]).strip()}\n" return response except Exception as e: raise Exception(f"SQL Rewards Error: {e}") def get_tabel_report_text(card_id: str, year: int, month: int) -> str: """Формирует отчет по табелю из UOV_SELFSERVICE_TB_TABEL на основе CARD_ID""" try: conn = get_db_connection() cursor = conn.cursor() cursor.execute("SELECT e.ID FROM UOV_SELFSERVICE_PR_EMP e WHERE e.CARD_ID = %s", (card_id,)) row = cursor.fetchone() if not row: conn.close() return "📋 Рабочий табель:\n\nСотрудник не найден в базе данных." emp_id = row[0] query = """ SELECT e.D, e.TDAY_ID, e.H FROM UOV_SELFSERVICE_TB_TABEL e WHERE e.EMP_ID = %s AND YEAR(e.D) = %s AND MONTH(e.D) = %s ORDER BY e.D ASC """ cursor.execute(query, (emp_id, year, month)) records = cursor.fetchall() conn.close() if not records: return f"📋 Рабочий табель за {month:02d}.{year} г.\n\nДанные отсутствуют." tabel_map = {r[0].day if hasattr(r[0], 'day') else int(str(r[0])[-2:]): (str(r[1]).strip(), float(r[2]) if r[2] is not None else 0.0) for r in records} total_days = 0 total_hours = 0.0 for code, hours in tabel_map.values(): if code == 'Я': total_days += 1 total_hours += hours months_names = {1: "Январь", 2: "Февраль", 3: "Март", 4: "Апрель", 5: "Май", 6: "Июнь", 7: "Июль", 8: "Август", 9: "Сентябрь", 10: "Октябрь", 11: "Ноябрь", 12: "Декабрь"} report = ( f"📅 Рабочий табель за {months_names.get(month, 'Указанный месяц')} {year} г.\n\n" f"📊 Итого за месяц:\n" f"• Отработано дней: {total_days}\n" f"• Всего часов: {total_hours:.1f} ч.\n" f"• Переработки / Недоработки: 0 ч.\n" ) days_in_month = calendar.monthrange(year, month)[1] weekday_ru = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"] current_week_days = [] week_counter = 1 for day in range(1, days_in_month + 1): cur_date = datetime(year, month, day) wday_idx = cur_date.weekday() code, hours = tabel_map.get(day, ('В', 0.0)) current_week_days.append((day, weekday_ru[wday_idx], code, hours)) if wday_idx == 6 or day == days_in_month: w_start = f"{current_week_days[0][0]:02d}.{month:02d}" w_end = f"{current_week_days[-1][0]:02d}.{month:02d}" report += f"\n🔹 Неделя {week_counter} ({w_start} — {w_end}):\n" idx = 0 while idx < len(current_week_days): d_num, d_name, c_code, c_hours = current_week_days[idx] if c_code == 'Я' and c_hours == 8.2: run_start = d_name run_end = d_name count = 1 while idx + 1 < len(current_week_days) and current_week_days[idx+1][2] == 'Я' and current_week_days[idx+1][3] == 8.2: idx += 1 run_end = current_week_days[idx][1] count += 1 range_str = f"{run_start} - {run_end}" if count > 1 else f"{run_start}" report += f"• {range_str}: 🛠 Явка ({count} дн. по 8.2 ч.)\n" elif c_code == 'В' and c_hours == 0.0: run_start = d_name run_end = d_name count = 1 while idx + 1 < len(current_week_days) and current_week_days[idx+1][2] == 'В' and current_week_days[idx+1][3] == 0.0: idx += 1 run_end = current_week_days[idx][1] count += 1 range_str = f"{run_start} - {run_end}" if count > 1 else f"{run_start}" report += f"• {range_str}: 🏖 Выходные (0 ч.)\n" else: emoji = "🛠" if c_code == 'Я' else "⏱" if c_code in ['Б', 'БЛ'] else "🏖" label = "Явка" if c_code == 'Я' else "Больничный" if c_code in ['Б', 'БЛ'] else "Отпуск" if c_code == 'ОТ' else "Выходной" report += f"• {d_name}: {emoji} {label} ({c_hours} ч.)\n" idx += 1 current_week_days = [] week_counter += 1 return report except Exception as e: raise Exception(f"SQL Tabel Parser Error: {e}")