Initial commit: TrueConf Chatbot КЛЕВЕР
This commit is contained in:
@@ -0,0 +1,378 @@
|
||||
# /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}")
|
||||
Reference in New Issue
Block a user