Files
trueconf_bot/lk/info_service.py

378 lines
18 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# /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 "🌴 <b>Информация об отпусках</b>\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 = "🌴 <b>Информация об отпусках</b>\n\n🟢 <b>Доступный остаток:</b>\n"
if balances:
for b in balances:
b_name = str(b[0]).strip() if b[0] else "Ежегодный оплачиваемый отпуск"
static_header += f"• <b>{int(b[1]) if b[1] else 0} дн.</b> <i>({b_name})</i>\n"
else:
static_header += "• <b>0 дн.</b>\n"
static_header += "\n⏳ <b>Предстоящие отпуска:</b>\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"• <b>{d_from}</b> — <b>{int(p[1]) if p[1] else 0} дн.</b>\n"
else:
static_header += "• <i>Нет запланированных отпусков</i>\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} : 🔹 <b>{h_days} дн.</b> <i>({type_str})</i>")
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 "📑 <b>Ваши больничные листы:</b>\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 "📑 <b>Ваши больничные листы:</b>\n\nНичего не найдено."
response = "📑 <b>Ваши больничные листы:</b>\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" — <b>{delta_days} дн.</b>"
response += f"🩺 <code>{d_from}</code> по <code>{d_to}</code>{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"• 📅 <code>{d_from}</code> — <code>{d_to}</code>\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"• ⏳ <b>{d_from}{d_to}</b>\n 💼 <b>Должность:</b> {appoint}\n 📂 <b>Отдел:</b> {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 "🏆 <b>Ваши награды и поощрения:</b>\n\nНичего не найдено."
response = "🏆 <b>Ваши награды и поощрения:</b>\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"🏅 <b>{date_str}</b> — {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 "📋 <b>Рабочий табель:</b>\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"📋 <b>Рабочий табель за {month:02d}.{year} г.</b>\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"📅 <b>Рабочий табель за {months_names.get(month, 'Указанный месяц')} {year} г.</b>\n\n"
f"📊 <b>Итого за месяц:</b>\n"
f"• Отработано дней: <b>{total_days}</b>\n"
f"• Всего часов: <b>{total_hours:.1f} ч.</b>\n"
f"• Переработки / Недоработки: <b>0 ч.</b>\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🔹 <b>Неделя {week_counter} ({w_start}{w_end}):</b>\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}: 🛠 <b>Явка</b> ({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}: 🏖 <b>Выходные</b> (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} <b>{label}</b> ({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}")