Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c7ae88a3e0 | |||
| 7b129e0836 | |||
| 9a9b90b3a1 | |||
| 95803b2c3c | |||
| 809f5f4980 | |||
| fbe9c042ce | |||
| 508bfd36df |
Binary file not shown.
Binary file not shown.
+171
-191
@@ -1,262 +1,242 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Диагностика данных сотрудника для модуля ЛК (расчетnye listki)."""
|
"""Диагностика данных сотрудника для модуля ЛК (расчетные листки)."""
|
||||||
|
|
||||||
|
import calendar
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 1. Загрузка .env и путей
|
||||||
|
# ============================================================
|
||||||
BASE_DIR = "/opt/trueconf_bot"
|
BASE_DIR = "/opt/trueconf_bot"
|
||||||
os.chdir(BASE_DIR)
|
if os.path.exists(BASE_DIR):
|
||||||
|
os.chdir(BASE_DIR)
|
||||||
|
else:
|
||||||
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
# Загрузка .env
|
|
||||||
def load_env(filepath):
|
def load_env(filepath):
|
||||||
with open(filepath, "r", encoding="utf-8") as f:
|
try:
|
||||||
for line in f:
|
with open(filepath, "r", encoding="utf-8") as f:
|
||||||
line = line.strip()
|
for line in f:
|
||||||
if line and not line.startswith("#") and "=" in line:
|
line = line.strip()
|
||||||
key, value = line.split("=", 1)
|
if line and not line.startswith("#") and "=" in line:
|
||||||
value = value.strip(' "')
|
key, value = line.split("=", 1)
|
||||||
value = value.strip("'")
|
os.environ[key.strip()] = value.strip(' "\'\r\n')
|
||||||
value = value.strip()
|
except FileNotFoundError:
|
||||||
os.environ[key.strip()] = value
|
print(f"⚠️ Файл {filepath} не найден.")
|
||||||
|
|
||||||
load_env(os.path.join(BASE_DIR, "config", ".env"))
|
load_env(os.path.join(BASE_DIR, "config", ".env"))
|
||||||
|
|
||||||
# Загрузка passwork
|
# ============================================================
|
||||||
for pw_path in ["/opt/passwork", os.path.join(BASE_DIR, "passwork")]:
|
# 2. Загрузка паролей из Passwork (AD и SQL)
|
||||||
|
# ============================================================
|
||||||
|
CREDENTIALS_MAP = {
|
||||||
|
"AD_PASSWORD": os.getenv("PW_ID_AD", "").strip(' "\'\r\n'),
|
||||||
|
"DB_PASSWORD": os.getenv("PW_ID_SQL", "").strip(' "\'\r\n'),
|
||||||
|
}
|
||||||
|
|
||||||
|
passwork_paths = [
|
||||||
|
"/opt/passwork",
|
||||||
|
os.path.join(BASE_DIR, "passwork"),
|
||||||
|
os.path.join(BASE_DIR, "config", "passwork"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for pw_path in passwork_paths:
|
||||||
if os.path.isdir(pw_path) and pw_path not in sys.path:
|
if os.path.isdir(pw_path) and pw_path not in sys.path:
|
||||||
sys.path.insert(0, pw_path)
|
sys.path.insert(0, pw_path)
|
||||||
|
|
||||||
from passwork import get_passwork_secrets
|
if BASE_DIR not in sys.path:
|
||||||
card_name = os.getenv("PW_ID_SQL", "").strip()
|
sys.path.insert(0, BASE_DIR)
|
||||||
passwork_pool = get_passwork_secrets(required_cards=[card_name])
|
|
||||||
card_data = passwork_pool[card_name]
|
AD_USER, AD_PASSWORD, DB_USER, DB_PASSWORD = "", "", "", ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
from passwork import get_passwork_secrets
|
||||||
|
required_cards = [name for name in CREDENTIALS_MAP.values() if name]
|
||||||
|
passwork_pool = get_passwork_secrets(required_cards=required_cards)
|
||||||
|
|
||||||
|
ad_card_name = CREDENTIALS_MAP.get("AD_PASSWORD")
|
||||||
|
if ad_card_name and ad_card_name in passwork_pool:
|
||||||
|
card_ad = passwork_pool[ad_card_name]
|
||||||
|
AD_USER = card_ad.get("login", "").strip(' "\'\r\n')
|
||||||
|
AD_PASSWORD = card_ad.get("password", "").strip(' "\'\r\n')
|
||||||
|
|
||||||
|
sql_card_name = CREDENTIALS_MAP.get("DB_PASSWORD")
|
||||||
|
if sql_card_name and sql_card_name in passwork_pool:
|
||||||
|
card_sql = passwork_pool[sql_card_name]
|
||||||
|
DB_USER = card_sql.get("login", "").strip(' "\'\r\n')
|
||||||
|
DB_PASSWORD = card_sql.get("password", "").strip(' "\'\r\n')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Ошибка загрузки из Passwork: {e}")
|
||||||
|
|
||||||
|
if not AD_USER: AD_USER = os.getenv("AD_USER", "")
|
||||||
|
if not AD_PASSWORD: AD_PASSWORD = os.getenv("AD_PASSWORD", "")
|
||||||
|
if not DB_USER: DB_USER = os.getenv("DB_USER", "")
|
||||||
|
if not DB_PASSWORD: DB_PASSWORD = os.getenv("DB_PASSWORD", "")
|
||||||
|
|
||||||
DB_USER = card_data["login"]
|
|
||||||
DB_PASSWORD = card_data["password"]
|
|
||||||
SQL_SERVER = os.getenv("SQL_SERVER", "SRVKEM-MOBILEIN.sibcem.ru")
|
SQL_SERVER = os.getenv("SQL_SERVER", "SRVKEM-MOBILEIN.sibcem.ru")
|
||||||
SQL_DB_NAME = os.getenv("SQL_DB_NAME", "BossCopy")
|
SQL_DB_NAME = os.getenv("SQL_DB_NAME", "BossCopy")
|
||||||
|
AD_SERVER = os.getenv("AD_SERVER", "172.16.20.20")
|
||||||
|
AD_BASE = os.getenv("AD_BASE", "DC=sibcem,DC=ru")
|
||||||
|
|
||||||
import pymssql
|
|
||||||
import ldap3
|
import ldap3
|
||||||
import calendar
|
import pymssql
|
||||||
|
|
||||||
# --- Подключение к AD ---
|
|
||||||
print("=" * 60)
|
|
||||||
print("Диагностика сотрудника: la.guseynova@sibcem.ru")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 3. Поиск сотрудника в Active Directory
|
||||||
|
# ============================================================
|
||||||
email = "la.guseynova@sibcem.ru"
|
email = "la.guseynova@sibcem.ru"
|
||||||
search_id = email.replace("@tcs.sibcem.ru", "@sibcem.ru")
|
search_id = email.replace("@tcs.sibcem.ru", "@sibcem.ru")
|
||||||
short_name = search_id.split("@")[0]
|
short_name = search_id.split("@")[0]
|
||||||
|
|
||||||
print(f"\n--- 0. Поиск в AD ---")
|
print("=" * 60)
|
||||||
print(f" Email: {email}")
|
print(f"Диагностика сотрудника: {email}")
|
||||||
print(f" Search ID: {search_id}")
|
print("=" * 60)
|
||||||
print(f" Short (без домена): {short_name}")
|
|
||||||
|
|
||||||
AD_SERVER = "172.16.20.20"
|
print("\n--- 0. Поиск в AD ---")
|
||||||
AD_BASE = "DC=sibcem,DC=ru"
|
card_id_ad = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
server = ldap3.Server(AD_SERVER, connect_timeout=5)
|
||||||
|
bind_user = AD_USER
|
||||||
|
if bind_user and "@" not in bind_user and "\\" not in bind_user:
|
||||||
|
bind_user = f"{bind_user}@sibcem.ru"
|
||||||
|
|
||||||
conn = ldap3.Connection(
|
conn = ldap3.Connection(
|
||||||
ldap3.Server(AD_SERVER, connect_timeout=5),
|
server,
|
||||||
|
user=bind_user,
|
||||||
|
password=AD_PASSWORD,
|
||||||
auto_bind=True,
|
auto_bind=True,
|
||||||
read_only=True
|
auto_referrals=False
|
||||||
)
|
)
|
||||||
|
|
||||||
search_filter = f"(|(mail={search_id}*)(sAMAccountName={short_name})(userPrincipalName={search_id}*)(anr={short_name}))"
|
attributes_list = ["cn", "sAMAccountName", "mail", "userPrincipalName", "proxyAddresses", "extensionAttribute2"]
|
||||||
print(f" LDAP-фильтр: {search_filter}")
|
search_filter = f"(|(sAMAccountName={short_name})(mail={search_id})(userPrincipalName={search_id})(proxyAddresses=*:{search_id}*)(proxyAddresses=*{short_name}*)(anr={short_name}))"
|
||||||
|
|
||||||
conn.search(
|
conn.search(
|
||||||
search_base=AD_BASE,
|
search_base=AD_BASE,
|
||||||
search_filter=search_filter,
|
search_filter=search_filter,
|
||||||
attributes=["cn", "sAMAccountName", "mail", "userPrincipalName", "extensionAttribute2", "mobile", "personalPager"]
|
attributes=attributes_list
|
||||||
)
|
)
|
||||||
|
|
||||||
if conn.entries:
|
if conn.entries:
|
||||||
entry = conn.entries[0]
|
entry = conn.entries[0]
|
||||||
cn = str(entry.cn)
|
cn = str(entry.cn)
|
||||||
sam = str(entry.sAMAccountName)
|
sam = str(entry.sAMAccountName) if 'sAMAccountName' in entry else ""
|
||||||
mail = str(entry.mail) if 'mail' in entry else ""
|
mail_val = str(entry.mail) if 'mail' in entry else ""
|
||||||
upn = str(entry.userPrincipalName) if 'userPrincipalName' in entry else ""
|
upn = str(entry.userPrincipalName) if 'userPrincipalName' in entry else ""
|
||||||
ext2 = str(entry.extensionAttribute2) if 'extensionAttribute2' in entry else ""
|
card_id_ad = str(entry.extensionAttribute2) if 'extensionAttribute2' in entry else None
|
||||||
mobile = str(entry.mobile) if 'mobile' in entry else ""
|
|
||||||
pager = str(entry.personalPager) if 'personalPager' in entry else ""
|
|
||||||
|
|
||||||
print(f"\n НАЙДЕН:")
|
print(" ✅ НАЙДЕН В AD!")
|
||||||
print(f" CN: {cn}")
|
print(f" CN: {cn}")
|
||||||
print(f" sAMAccountName: {sam}")
|
print(f" sAMAccountName: {sam}")
|
||||||
print(f" mail: {mail}")
|
print(f" mail: {mail_val}")
|
||||||
print(f" UPN: {upn}")
|
print(f" UPN: {upn}")
|
||||||
print(f" extensionAttribute2 (CARD_ID): {ext2}")
|
print(f" extensionAttribute2 (CARD_ID из AD): {card_id_ad}")
|
||||||
print(f" mobile: {mobile}")
|
|
||||||
print(f" pager: {pager}")
|
|
||||||
else:
|
else:
|
||||||
print(" Не найден!")
|
print(" ❌ НЕ НАЙДЕН в AD!")
|
||||||
conn.unbind()
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
conn.unbind()
|
conn.unbind()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" Ошибка подключения к AD: {e}")
|
print(f" ❌ Ошибка обращения к AD: {e}")
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
# --- Подключение к SQL ---
|
# ============================================================
|
||||||
print(f"\n--- 1. Подключение к SQL ---")
|
# 4. Подключение к SQL Server
|
||||||
|
# ============================================================
|
||||||
|
print("\n--- 1. Подключение к SQL Server ---")
|
||||||
try:
|
try:
|
||||||
conn_sql = pymssql.connect(
|
conn_sql = pymssql.connect(
|
||||||
server=SQL_SERVER,
|
server=SQL_SERVER,
|
||||||
user=DB_USER,
|
user=DB_USER,
|
||||||
password=DB_PASSWORD,
|
password=DB_PASSWORD,
|
||||||
database=SQL_DB_NAME,
|
database=SQL_DB_NAME,
|
||||||
charset="cp1251",
|
charset='cp1251',
|
||||||
login_timeout=10
|
login_timeout=10
|
||||||
)
|
)
|
||||||
cursor = conn_sql.cursor()
|
cursor = conn_sql.cursor()
|
||||||
print(f" Подключено к {SQL_SERVER}/{SQL_DB_NAME}")
|
print(" ✅ Подключено к SQL Server!")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" Ошибка подключения к SQL: {e}")
|
print(f" ❌ Ошибка подключения к SQL: {e}")
|
||||||
sys.exit(0)
|
sys.exit(1)
|
||||||
|
|
||||||
# --- Получаем CARD_ID ---
|
# ============================================================
|
||||||
card_id = None
|
# 5. Определение CARD_ID
|
||||||
print(f"\n--- 2. Поиск CARD_ID в PR_CARD ---")
|
# ============================================================
|
||||||
cursor.execute(
|
card_id = card_id_ad
|
||||||
"SELECT c.ID FROM UOV_SELFSERVICE_PR_CARD c "
|
|
||||||
"JOIN UOV_SELFSERVICE_PR_EMP e ON e.CARD_ID = c.ID "
|
|
||||||
"WHERE e.EMAIL_ID LIKE %s OR e.USERNAME LIKE %s",
|
|
||||||
(f"%{short_name}%", f"%{short_name}%")
|
|
||||||
)
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
if rows:
|
|
||||||
for r in rows:
|
|
||||||
print(f" CARD_ID (по username): {r[0]}")
|
|
||||||
card_id = rows[0][0]
|
|
||||||
else:
|
|
||||||
print(f" Не найден по username")
|
|
||||||
|
|
||||||
if not card_id:
|
if not card_id:
|
||||||
cursor.execute(
|
print("\n--- 2. Поиск CARD_ID по ФИО в PR_CARD ---")
|
||||||
"SELECT c.ID FROM UOV_SELFSERVICE_PR_CARD c "
|
cursor.execute("SELECT c.ID, c.NAME FROM UOV_SELFSERVICE_PR_CARD c WHERE c.NAME LIKE %s", ("%Гусейнова%",))
|
||||||
"JOIN UOV_SELFSERVICE_PR_EMP e ON e.CARD_ID = c.ID "
|
|
||||||
"WHERE e.EMAIL_ID LIKE %s",
|
|
||||||
(f"%{search_id.split('@')[0]}%",)
|
|
||||||
)
|
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
if rows:
|
if rows:
|
||||||
for r in rows:
|
card_id = str(rows[0][0])
|
||||||
print(f" CARD_ID (по email): {r[0]}")
|
print(f" ✅ НАЙДЕН по ФИО: CARD_ID={card_id} ({rows[0][1]})")
|
||||||
card_id = rows[0][0]
|
|
||||||
else:
|
if not card_id:
|
||||||
print(f" CARD_ID не найден!")
|
print(" ❌ Не удалось определить CARD_ID!")
|
||||||
conn_sql.close()
|
conn_sql.close()
|
||||||
sys.exit(0)
|
sys.exit(1)
|
||||||
|
|
||||||
print(f"\n Используем CARD_ID: {card_id}")
|
print(f"\n Используем CARD_ID: {card_id}")
|
||||||
|
|
||||||
# --- PR_EMP ---
|
# ============================================================
|
||||||
print(f"\n--- 3. PR_EMP (профиль сотрудника) ---")
|
# 6. Проверка доступных периодов (get_latest_payslip_months)
|
||||||
cursor.execute(
|
# ============================================================
|
||||||
"SELECT ID, FIRM_ID, EMAIL_ID, USERNAME FROM UOV_SELFSERVICE_PR_EMP WHERE CARD_ID = %s",
|
print("\n--- 3. Расчетные периоды (SL_YTAX) ---")
|
||||||
(card_id,)
|
q_months = """
|
||||||
)
|
SELECT DISTINCT TOP 6 (t.TYEAR * 12 + t.TMONTH) as CMONTH, t.TYEAR, t.TMONTH
|
||||||
emp_rows = cursor.fetchall()
|
FROM UOV_SELFSERVICE_PR_EMP e
|
||||||
if emp_rows:
|
JOIN UOV_SELFSERVICE_PR_CARD c on c.ID = e.CARD_ID
|
||||||
for r in emp_rows:
|
JOIN UOV_SELFSERVICE_SL_YTAX t on t.CARD_ID = e.CARD_ID
|
||||||
print(f" EMP_ID: {r[0]}, FIRM_ID: {r[1]}, EMAIL: {r[2]}, USERNAME: {r[3]}")
|
JOIN UOV_SELFSERVICE_PR_TRANS tr on tr.EMP_ID = e.ID
|
||||||
|
JOIN UOV_SELFSERVICE_ST_APPOINT a on a.ID = tr.APPOINT_ID
|
||||||
|
JOIN UOV_SELFSERVICE_HR_FIRM f on f.ID = e.FIRM_ID
|
||||||
|
WHERE c.ID = %s
|
||||||
|
ORDER BY CMONTH DESC
|
||||||
|
"""
|
||||||
|
cursor.execute(q_months, (card_id,))
|
||||||
|
month_rows = cursor.fetchall()
|
||||||
|
|
||||||
|
if not month_rows:
|
||||||
|
print(" ❌ Отсутствуют расчетные периоды для данного CARD_ID!")
|
||||||
|
conn_sql.close()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f" Найдено периодов: {len(month_rows)}")
|
||||||
|
for r in month_rows:
|
||||||
|
print(f" • CMONTH={r[0]} ({r[1]}-{r[2]:02d})")
|
||||||
|
|
||||||
|
target_cmonth = month_rows[0][0]
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 7. Тест определения организации (q_firm) для периода
|
||||||
|
# ============================================================
|
||||||
|
print(f"\n--- 4. Определение FIRM_ID для CMONTH={target_cmonth} ---")
|
||||||
|
tyear = (target_cmonth - 1) // 12
|
||||||
|
tmonth = (target_cmonth - 1) % 12 + 1
|
||||||
|
_, last_day = calendar.monthrange(tyear, tmonth)
|
||||||
|
|
||||||
|
date_start = f"{tyear}-{tmonth:02d}-01"
|
||||||
|
date_end = f"{tyear}-{tmonth:02d}-{last_day:02d}"
|
||||||
|
date_params = (date_start, date_start, date_end, date_end, date_end)
|
||||||
|
|
||||||
|
q_firm = """
|
||||||
|
select e.FIRM_ID, f.SNAME from (select e.id, e.FIRM_ID from UOV_SELFSERVICE_PR_EMP e
|
||||||
|
join UOV_SELFSERVICE_PR_CARD c on c.ID=e.CARD_ID where c.ID = %s
|
||||||
|
group by e.ID, e.FIRM_ID ) 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
|
||||||
|
where ((t.D_FROM <= %s and t.D_TO >= %s) or (t.D_FROM <= %s and t.D_TO >= %s) or (t.D_FROM <= %s and t.D_TO = '1999-12-31'))
|
||||||
|
group by e.FIRM_ID, f.SNAME
|
||||||
|
"""
|
||||||
|
cursor.execute(q_firm, (card_id, *date_params))
|
||||||
|
firm_row = cursor.fetchone()
|
||||||
|
|
||||||
|
if firm_row:
|
||||||
|
print(f" ✅ q_firm УСПЕШНО определил FIRM_ID={firm_row[0]} ({firm_row[1]}) за {tyear}-{tmonth:02d}")
|
||||||
else:
|
else:
|
||||||
print(" ПУСТО - нет записей в PR_EMP")
|
print(f" ❌ q_firm НЕ СМОГ определить FIRM_ID за {tyear}-{tmonth:02d}!")
|
||||||
|
|
||||||
# --- PR_TRANS ---
|
|
||||||
print(f"\n--- 4. PR_TRANS (кадровые назначения) ---")
|
|
||||||
if emp_rows:
|
|
||||||
emp_ids = [r[0] for r in emp_rows]
|
|
||||||
placeholders = ",".join(["%s"] * len(emp_ids))
|
|
||||||
cursor.execute(
|
|
||||||
f"SELECT EMP_ID, D_FROM, D_TO, TP_ABBR, SYSPAY_ID "
|
|
||||||
f"FROM UOV_SELFSERVICE_PR_TRANS "
|
|
||||||
f"WHERE EMP_ID IN ({placeholders}) "
|
|
||||||
f"ORDER BY D_FROM DESC",
|
|
||||||
emp_ids
|
|
||||||
)
|
|
||||||
trans_rows = cursor.fetchall()
|
|
||||||
if trans_rows:
|
|
||||||
print(f" Найдено записей: {len(trans_rows)}")
|
|
||||||
for r in trans_rows[:10]:
|
|
||||||
print(f" EMP_ID: {r[0]}, D_FROM: {r[1]}, D_TO: {r[2]}, TP: {r[3]}, SYSPAY: {r[4]}")
|
|
||||||
else:
|
|
||||||
print(" ПУСТО - нет записей в PR_TRANS")
|
|
||||||
else:
|
|
||||||
print(" Пропуск (нет PR_EMP)")
|
|
||||||
|
|
||||||
# --- SL_YTAX ---
|
|
||||||
print(f"\n--- 5. SL_YTAX (периоды с данными) ---")
|
|
||||||
cursor.execute(
|
|
||||||
"SELECT DISTINCT TYEAR, TMONTH, CMONTH FROM UOV_SELFSERVICE_SL_YTAX "
|
|
||||||
"WHERE CARD_ID = %s ORDER BY CMONTH DESC",
|
|
||||||
(card_id,)
|
|
||||||
)
|
|
||||||
tax_rows = cursor.fetchall()
|
|
||||||
if tax_rows:
|
|
||||||
print(f" Найдено периодов: {len(tax_rows)}")
|
|
||||||
for r in tax_rows:
|
|
||||||
print(f" {r[0]}-{r[1]:02d} (CMONTH={r[2]})")
|
|
||||||
else:
|
|
||||||
print(" ПУСТО - нет периодов в SL_YTAX")
|
|
||||||
|
|
||||||
# --- 6. Проверка q_firm для ВСЕХ периодов ---
|
|
||||||
print(f"\n--- 6. Проверка q_firm для КАЖДОГО периода ---")
|
|
||||||
if tax_rows:
|
|
||||||
for tax_row in tax_rows:
|
|
||||||
tyear = tax_row[0]
|
|
||||||
tmonth = tax_row[1]
|
|
||||||
cmonth = tax_row[2]
|
|
||||||
_, last_day = calendar.monthrange(tyear, tmonth)
|
|
||||||
date_start = f"{tyear}-{tmonth:02d}-01"
|
|
||||||
date_end = f"{tyear}-{tmonth:02d}-{last_day:02d}"
|
|
||||||
date_params = (date_start, date_start, date_end, date_end, date_end)
|
|
||||||
|
|
||||||
q_firm = """
|
|
||||||
select e.FIRM_ID, f.SNAME from (select e.id, e.FIRM_ID from UOV_SELFSERVICE_PR_EMP e
|
|
||||||
join UOV_SELFSERVICE_PR_CARD c on c.ID=e.CARD_ID where c.ID = %s
|
|
||||||
group by e.ID, e.FIRM_ID ) 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
|
|
||||||
where ((t.D_FROM <= %s and t.D_TO >= %s) or (t.D_FROM <= %s and t.D_TO >= %s) or (t.D_FROM <= %s and t.D_TO = '1999-12-31'))
|
|
||||||
group by e.FIRM_ID, f.SNAME
|
|
||||||
"""
|
|
||||||
cursor.execute(q_firm, (card_id, *date_params))
|
|
||||||
firm_row = cursor.fetchone()
|
|
||||||
if firm_row:
|
|
||||||
print(f" {tyear}-{tmonth:02d} (CMONTH={cmonth}): ✅ FIRM_ID={firm_row[0]}, SNAME={firm_row[1]}")
|
|
||||||
else:
|
|
||||||
print(f" {tyear}-{tmonth:02d} (CMONTH={cmonth}): ❌ q_firm НЕ НАШЁЛ FIRM_ID!")
|
|
||||||
|
|
||||||
# Фоллбэк
|
|
||||||
q_firm_fb = """
|
|
||||||
select e.FIRM_ID from UOV_SELFSERVICE_PR_EMP e
|
|
||||||
join UOV_SELFSERVICE_PR_CARD c on c.ID=e.CARD_ID
|
|
||||||
where c.ID = %s
|
|
||||||
group by e.ID, e.FIRM_ID
|
|
||||||
"""
|
|
||||||
cursor.execute(q_firm_fb, (card_id,))
|
|
||||||
fb_row = cursor.fetchone()
|
|
||||||
if fb_row:
|
|
||||||
print(f" Фоллбэк: FIRM_ID={fb_row[0]}")
|
|
||||||
else:
|
|
||||||
print(f" Фоллбэк НЕ НАШЁЛ - критическая ошибка!")
|
|
||||||
else:
|
|
||||||
print(" Нет периодов для проверки")
|
|
||||||
|
|
||||||
# --- 7. Сводка ---
|
|
||||||
print(f"\n--- 7. Сводка ---")
|
|
||||||
cursor.execute(
|
|
||||||
"SELECT COUNT(DISTINCT CMONTH), MIN(CMONTH), MAX(CMONTH) FROM UOV_SELFSERVICE_SL_YTAX WHERE CARD_ID = %s",
|
|
||||||
(card_id,)
|
|
||||||
)
|
|
||||||
summary = cursor.fetchone()
|
|
||||||
if summary:
|
|
||||||
print(f" Периодов в SL_YTAX: {summary[0]}")
|
|
||||||
print(f" Диапазон: {summary[1]} - {summary[2]}")
|
|
||||||
|
|
||||||
print(f"\n{'=' * 60}")
|
print(f"\n{'=' * 60}")
|
||||||
print("Диагностика завершена")
|
print("Диагностика завершена")
|
||||||
|
|||||||
Binary file not shown.
@@ -240,4 +240,3 @@ async def photo_module_handler(msg: Message):
|
|||||||
except Exception as cleanup_err:
|
except Exception as cleanup_err:
|
||||||
await asyncio.to_thread(log_and_notify_photo_error, user_id, "Очистка временных файлов", str(cleanup_err))
|
await asyncio.to_thread(log_and_notify_photo_error, user_id, "Очистка временных файлов", str(cleanup_err))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
Submodule
+1
Submodule sd_dispatcher added at b7b099cb38
Binary file not shown.
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import asyncio
|
||||||
|
import httpx
|
||||||
|
import json
|
||||||
|
import urllib3
|
||||||
|
|
||||||
|
BASE_URL = "https://192.168.1.237:8080"
|
||||||
|
API_TOKEN = "062CE056-C1F5-490C-A6DF-FD2C653A71DD"
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
url = f"{BASE_URL}/api/v3/requests"
|
||||||
|
headers = {
|
||||||
|
"authtoken": API_TOKEN,
|
||||||
|
"Accept": "application/vnd.manageengine.sdp.v3+json"
|
||||||
|
}
|
||||||
|
|
||||||
|
async with httpx.AsyncClient(verify=False, timeout=30.0) as client:
|
||||||
|
resp = await client.get(url, headers=headers, params={
|
||||||
|
"input_data": json.dumps({
|
||||||
|
"list_info": {"row_count": 100, "start_index": 1}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
data = resp.json()
|
||||||
|
print(f"Status: {resp.status_code}")
|
||||||
|
print(f"Has more: {data.get('list_info', {}).get('has_more_rows')}")
|
||||||
|
requests = data.get("requests", [])
|
||||||
|
print(f"Total: {len(requests)}")
|
||||||
|
for r in requests:
|
||||||
|
print(f" ID={r.get('id')} subject={r.get('subject')} created={r.get('created_time')}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
urllib3.disable_warnings()
|
||||||
|
asyncio.run(main())
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Проверка формата даты и вариантов фильтрации."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import httpx
|
||||||
|
import json
|
||||||
|
import urllib3
|
||||||
|
|
||||||
|
BASE_URL = "https://192.168.1.237:8080"
|
||||||
|
API_TOKEN = "062CE056-C1F5-490C-A6DF-FD2C653A71DD"
|
||||||
|
|
||||||
|
async def debug():
|
||||||
|
url = f"{BASE_URL}/api/v3/requests"
|
||||||
|
headers = {
|
||||||
|
"authtoken": API_TOKEN,
|
||||||
|
"Accept": "application/vnd.manageengine.sdp.v3+json"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Вариант 1: без фильтра, посмотреть все поля
|
||||||
|
print("=== Все заявки (без фильтра) ===")
|
||||||
|
async with httpx.AsyncClient(verify=False, timeout=30.0) as client:
|
||||||
|
resp = await client.get(url, headers=headers, params={
|
||||||
|
"input_data": json.dumps({
|
||||||
|
"list_info": {"row_count": 100, "start_index": 1}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
data = resp.json()
|
||||||
|
requests = data.get("requests", [])
|
||||||
|
|
||||||
|
for req in requests:
|
||||||
|
print(f"\n--- Заявка {req.get('request_id', '?')} ---")
|
||||||
|
print(f" subject: {req.get('subject')}")
|
||||||
|
print(f" created_time: {req.get('created_time')}")
|
||||||
|
print(f" created_time type: {type(req.get('created_time')).__name__}")
|
||||||
|
# Показать все ключи
|
||||||
|
print(f" Ключи: {list(req.keys())}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
asyncio.run(debug())
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Поиск рабочего формата фильтрации."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import httpx
|
||||||
|
import json
|
||||||
|
import urllib3
|
||||||
|
|
||||||
|
BASE_URL = "https://192.168.1.237:8080"
|
||||||
|
API_TOKEN = "062CE056-C1F5-490C-A6DF-FD2C653A71DD"
|
||||||
|
|
||||||
|
async def test_filters():
|
||||||
|
url = f"{BASE_URL}/api/v3/requests"
|
||||||
|
headers = {
|
||||||
|
"authtoken": API_TOKEN,
|
||||||
|
"Accept": "application/vnd.manageengine.sdp.v3+json"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Сначала посмотрим все заявки с их ID и датами
|
||||||
|
print("=== Все заявки ===")
|
||||||
|
async with httpx.AsyncClient(verify=False, timeout=30.0) as client:
|
||||||
|
resp = await client.get(url, headers=headers, params={
|
||||||
|
"input_data": json.dumps({
|
||||||
|
"list_info": {"row_count": 100, "start_index": 1}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
data = resp.json()
|
||||||
|
requests = data.get("requests", [])
|
||||||
|
|
||||||
|
for req in requests:
|
||||||
|
req_id = req.get("request_id", req.get("id", "?"))
|
||||||
|
subject = req.get("subject", "?")
|
||||||
|
created = req.get("created_time", {})
|
||||||
|
if isinstance(created, dict):
|
||||||
|
created_str = created.get("display_value", "?")
|
||||||
|
ts = created.get("value", "?")
|
||||||
|
else:
|
||||||
|
created_str = str(created)
|
||||||
|
ts = "?"
|
||||||
|
print(f" ID={req_id} created={created_str} ts={ts}")
|
||||||
|
|
||||||
|
# Попробуем разные форматы фильтрации
|
||||||
|
print("\n=== Попробуем фильтры ===")
|
||||||
|
|
||||||
|
filters = [
|
||||||
|
# Вариант 1: ISO строка
|
||||||
|
{
|
||||||
|
"search_criteria": {
|
||||||
|
"column_name": "created_time",
|
||||||
|
"condition": "between",
|
||||||
|
"values": ["Jul 1, 2026 00:00:00", "Jul 31, 2026 23:59:59"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
# Вариант 2: без search_criteria, с custom_field
|
||||||
|
{
|
||||||
|
"search_criteria": {
|
||||||
|
"column_name": "created_time",
|
||||||
|
"condition": "between",
|
||||||
|
"values": ["2026-07-01", "2026-07-31"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
# Вариант 3: timestamp как строка
|
||||||
|
{
|
||||||
|
"search_criteria": {
|
||||||
|
"column_name": "created_time",
|
||||||
|
"condition": "between",
|
||||||
|
"values": ["1782864000000", "1785542399000"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, criteria in enumerate(filters):
|
||||||
|
params = {
|
||||||
|
"input_data": json.dumps({
|
||||||
|
"list_info": {"row_count": 100, "start_index": 1},
|
||||||
|
"search_criteria": criteria.get("search_criteria")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
resp = await client.get(url, headers=headers, params=params)
|
||||||
|
data = resp.json()
|
||||||
|
reqs = data.get("requests", [])
|
||||||
|
print(f"\nФильтр {i+1}: {criteria['search_criteria']['values']} -> {len(reqs)} заявок")
|
||||||
|
for r in reqs:
|
||||||
|
print(f" {r.get('id')}: {r.get('subject')}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
asyncio.run(test_filters())
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Получение списка заявок из ServiceDesk Plus через API v3."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import httpx
|
||||||
|
import json
|
||||||
|
import urllib3
|
||||||
|
|
||||||
|
BASE_URL = "https://192.168.1.237:8080"
|
||||||
|
API_TOKEN = "062CE056-C1F5-490C-A6DF-FD2C653A71DD"
|
||||||
|
|
||||||
|
async def list_tickets():
|
||||||
|
url = f"{BASE_URL}/api/v3/requests"
|
||||||
|
headers = {
|
||||||
|
"authtoken": API_TOKEN,
|
||||||
|
"Accept": "application/vnd.manageengine.sdp.v3+json"
|
||||||
|
}
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"input_data": json.dumps({
|
||||||
|
"list_info": {
|
||||||
|
"row_count": 50,
|
||||||
|
"start_index": 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async with httpx.AsyncClient(verify=False, timeout=30.0) as client:
|
||||||
|
resp = await client.get(url, headers=headers, params=params)
|
||||||
|
data = resp.json()
|
||||||
|
|
||||||
|
requests = data.get("requests", [])
|
||||||
|
print(f"Всего заявок: {len(requests)}\n")
|
||||||
|
|
||||||
|
for req in requests:
|
||||||
|
req_id = req.get("request_id", req.get("id", "?"))
|
||||||
|
subject = req.get("subject", req.get("short_description", "?"))
|
||||||
|
|
||||||
|
# Группа
|
||||||
|
group = req.get("group", {})
|
||||||
|
if isinstance(group, dict):
|
||||||
|
group_name = group.get("name", "?")
|
||||||
|
else:
|
||||||
|
group_name = str(group)
|
||||||
|
|
||||||
|
# Статус
|
||||||
|
status = req.get("status", {})
|
||||||
|
if isinstance(status, dict):
|
||||||
|
status_name = status.get("name", "?")
|
||||||
|
else:
|
||||||
|
status_name = str(status)
|
||||||
|
|
||||||
|
# Приоритет
|
||||||
|
priority = req.get("priority", {})
|
||||||
|
if isinstance(priority, dict):
|
||||||
|
priority_name = priority.get("name", "?")
|
||||||
|
else:
|
||||||
|
priority_name = str(priority)
|
||||||
|
|
||||||
|
# Описание (убираем HTML)
|
||||||
|
desc = req.get("description", "")
|
||||||
|
desc_clean = desc.replace("<p>", "").replace("</p>", "").replace("<br>", "\n").replace("<br/>", "\n").replace("<br />", "\n")
|
||||||
|
desc_clean = desc_clean[:300]
|
||||||
|
|
||||||
|
# Создатель
|
||||||
|
requester = req.get("requester", {})
|
||||||
|
req_name = requester.get("name", "?") if isinstance(requester, dict) else "?"
|
||||||
|
|
||||||
|
print(f"--- Заявка #{req_id} ---")
|
||||||
|
print(f" Тема: {subject}")
|
||||||
|
print(f" Группа: {group_name}")
|
||||||
|
print(f" Статус: {status_name}")
|
||||||
|
print(f" Приоритет: {priority_name}")
|
||||||
|
print(f" Автор: {req_name}")
|
||||||
|
print(f" Описание:\n {desc_clean}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
asyncio.run(list_tickets())
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Дебаг запроса к ServiceDesk Plus API v3."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import httpx
|
||||||
|
import json
|
||||||
|
import urllib3
|
||||||
|
|
||||||
|
BASE_URL = "https://192.168.1.237:8080"
|
||||||
|
API_TOKEN = "062CE056-C1F5-490C-A6DF-FD2C653A71DD"
|
||||||
|
|
||||||
|
async def debug():
|
||||||
|
url = f"{BASE_URL}/api/v3/requests"
|
||||||
|
headers = {
|
||||||
|
"authtoken": API_TOKEN,
|
||||||
|
"Accept": "application/vnd.manageengine.sdp.v3+json"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Разные варианты формата
|
||||||
|
variants = [
|
||||||
|
# Вариант 1: input_data как JSON
|
||||||
|
{
|
||||||
|
"input_data": json.dumps({
|
||||||
|
"list_info": {
|
||||||
|
"row_count": 5,
|
||||||
|
"start_index": 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
# Вариант 2: без input_data, просто параметры
|
||||||
|
None,
|
||||||
|
# Вариант 3: input_data без json.dumps
|
||||||
|
{
|
||||||
|
"input_data": {
|
||||||
|
"list_info": {
|
||||||
|
"row_count": 5,
|
||||||
|
"start_index": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, params in enumerate(variants):
|
||||||
|
print(f"\n=== Вариант {i+1} ===")
|
||||||
|
print(f"Params: {params}")
|
||||||
|
|
||||||
|
async with httpx.AsyncClient(verify=False, timeout=15.0) as client:
|
||||||
|
try:
|
||||||
|
resp = await client.get(url, headers=headers, params=params)
|
||||||
|
print(f"HTTP {resp.status_code}")
|
||||||
|
print(f"Response: {resp.text[:500]}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
asyncio.run(debug())
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Получение списка заявок из ServiceDesk Plus с 20 июля 2026. Экспорт: Группа, Описание, Назначено, Категория в JSONL."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import httpx
|
||||||
|
import json
|
||||||
|
import urllib3
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from html import unescape
|
||||||
|
import re
|
||||||
|
|
||||||
|
BASE_URL = "https://192.168.1.237:8080"
|
||||||
|
API_TOKEN = "062CE056-C1F5-490C-A6DF-FD2C653A71DD"
|
||||||
|
|
||||||
|
FROM_DATE_MS = int(datetime(2026, 7, 20, 0, 0, 0, tzinfo=timezone.utc).timestamp() * 1000)
|
||||||
|
|
||||||
|
def get_ts(req):
|
||||||
|
ct = req.get("created_time", {})
|
||||||
|
if isinstance(ct, dict):
|
||||||
|
v = ct.get("value", "0")
|
||||||
|
return int(v) if v else 0
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def clean_html(text):
|
||||||
|
if not text:
|
||||||
|
return ""
|
||||||
|
text = re.sub(r'<[^>]+>', '', text)
|
||||||
|
text = unescape(text)
|
||||||
|
return text.strip()
|
||||||
|
|
||||||
|
def get_name(obj):
|
||||||
|
"""Извлечь имя из объекта группы/техника/категории."""
|
||||||
|
if not obj:
|
||||||
|
return ""
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
return obj.get("name", "") or obj.get("display_value", "")
|
||||||
|
return str(obj)
|
||||||
|
|
||||||
|
async def fetch_ticket_detail(client, req_id):
|
||||||
|
try:
|
||||||
|
resp = await client.get(
|
||||||
|
f"{BASE_URL}/api/v3/requests/{req_id}",
|
||||||
|
headers={"authtoken": API_TOKEN, "Accept": "application/vnd.manageengine.sdp.v3+json"},
|
||||||
|
timeout=15.0
|
||||||
|
)
|
||||||
|
data = resp.json()
|
||||||
|
return data.get("request", data)
|
||||||
|
except Exception as e:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
async def list_tickets():
|
||||||
|
url = f"{BASE_URL}/api/v3/requests"
|
||||||
|
headers = {"authtoken": API_TOKEN, "Accept": "application/vnd.manageengine.sdp.v3+json"}
|
||||||
|
|
||||||
|
async with httpx.AsyncClient(verify=False, timeout=30.0) as client:
|
||||||
|
resp = await client.get(url, headers=headers)
|
||||||
|
data = resp.json()
|
||||||
|
all_reqs = data.get("requests", [])
|
||||||
|
|
||||||
|
from_20 = [r for r in all_reqs if get_ts(r) >= FROM_DATE_MS]
|
||||||
|
|
||||||
|
# Получаем детали для каждой заявки
|
||||||
|
tasks = [fetch_ticket_detail(client, r.get("request_id", r.get("id", "?"))) for r in from_20]
|
||||||
|
details = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
for req, detail in zip(from_20, details):
|
||||||
|
# Группа — из списка (есть там)
|
||||||
|
group = req.get("group", {})
|
||||||
|
group_name = get_name(group)
|
||||||
|
|
||||||
|
# Описание — из детального запроса
|
||||||
|
desc = detail.get("description", "")
|
||||||
|
desc_clean = clean_html(desc)
|
||||||
|
|
||||||
|
# Назначено (специалист) — из детального запроса
|
||||||
|
tech = detail.get("technician", {})
|
||||||
|
assignee = get_name(tech)
|
||||||
|
|
||||||
|
# Категория — из детального запроса
|
||||||
|
cat = detail.get("category", {})
|
||||||
|
category = get_name(cat)
|
||||||
|
|
||||||
|
rows.append({
|
||||||
|
"group": group_name,
|
||||||
|
"description": desc_clean,
|
||||||
|
"assignee": assignee,
|
||||||
|
"category": category
|
||||||
|
})
|
||||||
|
|
||||||
|
# Выводим JSONL
|
||||||
|
for row in rows:
|
||||||
|
print(json.dumps(row, ensure_ascii=False))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
urllib3.disable_warnings()
|
||||||
|
asyncio.run(list_tickets())
|
||||||
Binary file not shown.
@@ -7,9 +7,7 @@ import httpx
|
|||||||
from ldap3 import Server, Connection, ALL
|
from ldap3 import Server, Connection, ALL
|
||||||
from trueconf import Router, Message
|
from trueconf import Router, Message
|
||||||
|
|
||||||
import config.config as config
|
|
||||||
from config.config import *
|
from config.config import *
|
||||||
from config.config import DEFAULT_EMAIL_FROM, SMTP_SERVER, SMTP_PORT, ALERTS_SUPPORT_EMAILS
|
|
||||||
from utils import states
|
from utils import states
|
||||||
from utils.menu import MENU_TEXT
|
from utils.menu import MENU_TEXT
|
||||||
from utils.texts import (
|
from utils.texts import (
|
||||||
@@ -186,4 +184,3 @@ async def transcription_handler(msg: Message):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,655 @@
|
|||||||
|
# /opt/trueconf_bot/utils/texts.py
|
||||||
|
# Все текстовые сообщения бота, отправляемые пользователю
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# GLOBAL CONSTANTS
|
||||||
|
# =========================================================
|
||||||
|
|
||||||
|
# --- Main Bot ---
|
||||||
|
# Общее для главного бота
|
||||||
|
UNKNOWN_MAIN_CMD_TEXT = (
|
||||||
|
"⚠️ <b>Неизвестная команда.</b>\n\n"
|
||||||
|
"Пожалуйста, используйте только цифры, соответствующие нужным пунктам меню, и отправьте выбранную цифру мне в сообщении."
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Emoji Digits ---
|
||||||
|
# Используются для навигации и выбора пунктов меню
|
||||||
|
EMOJI_DIGITS = {
|
||||||
|
"0": "0⃣",
|
||||||
|
"1": "1⃣",
|
||||||
|
"2": "2⃣",
|
||||||
|
"3": "3⃣",
|
||||||
|
"4": "4⃣",
|
||||||
|
"5": "5⃣",
|
||||||
|
"6": "6⃣",
|
||||||
|
"7": "7⃣",
|
||||||
|
"8": "8⃣",
|
||||||
|
"9": "9⃣",
|
||||||
|
"10": "🔟"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Footer Templates ---
|
||||||
|
# Шаблоны подвалов сообщений для навигации
|
||||||
|
|
||||||
|
def _footer(emojis: dict) -> str:
|
||||||
|
"""Стандартный футер с кнопками Назад и Меню"""
|
||||||
|
return f"\n\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
|
||||||
|
def _footer_main_only(emojis: dict) -> str:
|
||||||
|
"""Футер только с кнопкой Меню"""
|
||||||
|
return f"\n\n<i>{emojis['0']} — В главное меню</i>"
|
||||||
|
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# TRANSCRIPTION BOT
|
||||||
|
# =========================================================
|
||||||
|
# Модуль для расшифровки аудио и видео файлов
|
||||||
|
|
||||||
|
# --- Private functions (Internal logic) ---
|
||||||
|
|
||||||
|
def _transcription_welcome(emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
f"🎙 <b>Добро пожаловать! Я — Ассистент расшифровки совещаний.</b>\n\n"
|
||||||
|
f"Я помогу вам перевести аудио или видео в текст, а также подготовлю краткое резюме вашей встречи.\n\n"
|
||||||
|
f"📎 Для начала работы просто отправьте нужный файл прямо в этот чат.\n\n"
|
||||||
|
f"{emojis['0']} — В главное меню"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _transcription_unknown(emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
f"⚠️ <b>Неизвестная команда.</b>\n\n"
|
||||||
|
f"Пожалуйста, отправьте в сообщении мне нужную цифру соответствующего пункта меню в чат."
|
||||||
|
)
|
||||||
|
|
||||||
|
def _transcription_bad_extension(ext: str, emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
f"❌ <b>Ошибка: Недопустимый формат файла ({ext}).</b>\n\n"
|
||||||
|
f"Пожалуйста, отправьте поддерживаемый аудио или видео файл.\n\n"
|
||||||
|
f"{_footer(emojis)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _transcription_uploading() -> str:
|
||||||
|
return "⏳ <b>Загружаю файл...</b>"
|
||||||
|
|
||||||
|
def _transcription_success(email: str, emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
f"✅ <b>Хорошо, файл принят в обработку.</b>\n\n"
|
||||||
|
f"Как только всё будет готово, я отправлю вам результаты на почту (<code>{email}</code>).\n\n"
|
||||||
|
f"⏳ Обычно это занимает от 5 до 30 минут.\n\n"
|
||||||
|
f"{_footer(emojis)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _transcription_api_error(status_code: int, emojis: dict) -> str:
|
||||||
|
return f"❌ <b>Ошибка API сервера ({status_code}).</b>{_footer(emojis)}"
|
||||||
|
|
||||||
|
def _transcription_system_error(emojis: dict) -> str:
|
||||||
|
return f"❌ <b>Произошла системная ошибка при передаче файла.</b>{_footer(emojis)}"
|
||||||
|
|
||||||
|
# --- Public constants and functions ---
|
||||||
|
|
||||||
|
TRANSCRIPTION_MAIN_MENU_TEXT = _transcription_welcome(EMOJI_DIGITS)
|
||||||
|
TRANSCRIPTION_UNKNOWN_CMD_TEXT = _transcription_unknown(EMOJI_DIGITS)
|
||||||
|
|
||||||
|
def transcription_error_bad_extension(ext: str) -> str:
|
||||||
|
return _transcription_bad_extension(ext, EMOJI_DIGITS)
|
||||||
|
|
||||||
|
TRANSCRIPTION_UPLOADING_TEXT = _transcription_uploading()
|
||||||
|
|
||||||
|
def transcription_success_received(email: str) -> str:
|
||||||
|
return _transcription_success(email, EMOJI_DIGITS)
|
||||||
|
|
||||||
|
def transcription_error_api(status_code: int) -> str:
|
||||||
|
return _transcription_api_error(status_code, EMOJI_DIGITS)
|
||||||
|
|
||||||
|
def transcription_error_system() -> str:
|
||||||
|
return _transcription_system_error(EMOJI_DIGITS)
|
||||||
|
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# SERVICE DESK BOT
|
||||||
|
# =========================================================
|
||||||
|
# Модуль для создания и управления заявками в Service Desk
|
||||||
|
|
||||||
|
# --- Private functions (Internal logic) ---
|
||||||
|
|
||||||
|
def _sd_welcome(emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
f"🎫 <b>Режим создания заявки в Service Desk</b>\n\n"
|
||||||
|
f"• Опишите проблему текстом.\n"
|
||||||
|
f"• Можно прикрепить фото или документ.\n\n"
|
||||||
|
f"{emojis['0']} — В главное меню"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _sd_unknown(emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
f"⚠️ <b>Неизвестная команда.</b>\n"
|
||||||
|
f"Пожалуйста, используйте цифры меню из сообщения выше.\n\n"
|
||||||
|
f"{emojis['9']} — Назад\n"
|
||||||
|
f"{emojis['0']} — В главное меню"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _sd_text_required(emojis: dict) -> str:
|
||||||
|
return f"⚠️ <b>Сначала опишите проблему текстом.</b>"
|
||||||
|
|
||||||
|
def _sd_creating() -> str:
|
||||||
|
return "⏳ Создаю заявку, пожалуйста, подождите..."
|
||||||
|
|
||||||
|
def _sd_ticket_created(ticket_id: int, subject: str, description: str, emojis: dict) -> str:
|
||||||
|
clean_desc = description.replace("\n", " ").strip()
|
||||||
|
if len(clean_desc) > 150:
|
||||||
|
clean_desc = clean_desc[:147] + "..."
|
||||||
|
return (
|
||||||
|
f"✅ <b>Заявка #{ticket_id} создана!</b>\n\n"
|
||||||
|
f"📌 <b>Тема: {subject}</b>\n\n"
|
||||||
|
f"📝 <b>Текст: {clean_desc}</b>\n\n"
|
||||||
|
f"Если нужно добавить скриншоты или документ — отправьте их сейчас.\n\n"
|
||||||
|
f"{_footer(emojis)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _sd_ticket_create_error(emojis: dict) -> str:
|
||||||
|
return f"❌ <b>Ошибка создания заявки.</b>{_footer_main_only(emojis)}"
|
||||||
|
|
||||||
|
def _sd_system_error(emojis: dict) -> str:
|
||||||
|
return f"❌ <b>Системная ошибка.</b> Попробуйте позже.{_footer_main_only(emojis)}"
|
||||||
|
|
||||||
|
def _sd_uploading_file(ticket_id: int, emojis: dict) -> str:
|
||||||
|
return f"⏳ Загружаю файл в заявку #{ticket_id}..."
|
||||||
|
|
||||||
|
def _sd_file_attached(emojis: dict) -> str:
|
||||||
|
return f"✅ <b>Файл успешно прикреплен!</b>{_footer(emojis)}"
|
||||||
|
|
||||||
|
def _sd_file_upload_error(emojis: dict) -> str:
|
||||||
|
return f"❌ <b>Ошибка загрузки файла.</b>{_footer_main_only(emojis)}"
|
||||||
|
|
||||||
|
# --- Public constants and functions ---
|
||||||
|
|
||||||
|
SD_MAIN_MENU_TEXT = _sd_welcome(EMOJI_DIGITS)
|
||||||
|
SD_UNKNOWN_CMD_TEXT = _sd_unknown(EMOJI_DIGITS)
|
||||||
|
SD_TEXT_REQUIRED = _sd_text_required(EMOJI_DIGITS)
|
||||||
|
SD_CREATING = _sd_creating()
|
||||||
|
|
||||||
|
def sd_ticket_created(ticket_id: int, subject: str, description: str) -> str:
|
||||||
|
return _sd_ticket_created(ticket_id, subject, description, EMOJI_DIGITS)
|
||||||
|
|
||||||
|
def sd_ticket_create_error() -> str:
|
||||||
|
return _sd_ticket_create_error(EMOJI_DIGITS)
|
||||||
|
|
||||||
|
def sd_system_error() -> str:
|
||||||
|
return _sd_system_error(EMOJI_DIGITS)
|
||||||
|
|
||||||
|
def sd_uploading_file(ticket_id: int) -> str:
|
||||||
|
return _sd_uploading_file(ticket_id, EMOJI_DIGITS)
|
||||||
|
|
||||||
|
def sd_file_attached() -> str:
|
||||||
|
return _sd_file_attached(EMOJI_DIGITS)
|
||||||
|
|
||||||
|
def sd_file_upload_error() -> str:
|
||||||
|
return _sd_file_upload_error(EMOJI_DIGITS)
|
||||||
|
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# PHOTO BOT
|
||||||
|
# =========================================================
|
||||||
|
# Модуль для обновления фото профиля
|
||||||
|
|
||||||
|
# --- Private functions (Internal logic) ---
|
||||||
|
|
||||||
|
def _photo_welcome(emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
f"🖼 <b>Режим обновления фото профиля</b>\n\n"
|
||||||
|
f"📸 Пришлите мне вашу фотографию.\n\n"
|
||||||
|
f"{emojis['0']} — В главное меню"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _photo_unknown(emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
f"⚠️ <b>Неизвестная команда.</b>\n"
|
||||||
|
f"Пожалуйста, используйте цифры меню из сообщения выше.\n\n"
|
||||||
|
f"{emojis['9']} — Назад\n"
|
||||||
|
f"{emojis['0']} — В главное меню"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _photo_error_handling(emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
f"⚠️ <i>В данный момент выполнить это действие невозможно. "
|
||||||
|
f"Информация о проблеме передана ответственным.\n"
|
||||||
|
f"Приносим извинения за доставленные неудобства.</i>\n\n"
|
||||||
|
f"{emojis['0']} — В главное меню"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _photo_uploading() -> str:
|
||||||
|
return "⏳ Отправка..."
|
||||||
|
|
||||||
|
def _photo_success_sent() -> str:
|
||||||
|
return "📩 <b>Фото отправлено на утверждение.</b>"
|
||||||
|
|
||||||
|
def _photo_cancel(emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
f"🚫 Отменено. Пришлите другое фото.\n\n"
|
||||||
|
f"{emojis['0']} — В главное меню"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _photo_processing() -> str:
|
||||||
|
return "⏳ Обработка..."
|
||||||
|
|
||||||
|
def _photo_preview_ready(emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
f"... <b>Фото готово!</b>\n\n"
|
||||||
|
f"{emojis['1']} — Отправить на утверждение\n"
|
||||||
|
f"{emojis['2']} — Попробовать другое\n\n"
|
||||||
|
f"{emojis['0']} — В главное меню"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _photo_error_processing(error_text: str, emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
f"❌ <b>Ошибка:</b> {error_text}\n\n"
|
||||||
|
f"Пожалуйста, отправьте другое фото.\n\n"
|
||||||
|
f"{emojis['0']} — В главное меню"
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Public constants and functions ---
|
||||||
|
|
||||||
|
PHOTO_MAIN_MENU_TEXT = _photo_welcome(EMOJI_DIGITS)
|
||||||
|
PHOTO_UNKNOWN_CMD_TEXT = _photo_unknown(EMOJI_DIGITS)
|
||||||
|
|
||||||
|
def photo_error_handling() -> str:
|
||||||
|
return _photo_error_handling(EMOJI_DIGITS)
|
||||||
|
|
||||||
|
PHOTO_UPLOADING_TEXT = _photo_uploading()
|
||||||
|
|
||||||
|
def photo_success_sent() -> str:
|
||||||
|
return _photo_success_sent()
|
||||||
|
|
||||||
|
def photo_cancel_text() -> str:
|
||||||
|
return _photo_cancel(EMOJI_DIGITS)
|
||||||
|
|
||||||
|
PHOTO_PROCESSING_TEXT = _photo_processing()
|
||||||
|
|
||||||
|
def photo_preview_ready() -> str:
|
||||||
|
return _photo_preview_ready(EMOJI_DIGITS)
|
||||||
|
|
||||||
|
def photo_error_processing(error_text: str) -> str:
|
||||||
|
return _photo_error_processing(error_text, EMOJI_DIGITS)
|
||||||
|
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# SEARCH BOT
|
||||||
|
# =========================================================
|
||||||
|
# Модуль поиска по регламентам
|
||||||
|
|
||||||
|
# --- Private functions (Internal logic) ---
|
||||||
|
|
||||||
|
def _search_welcome(emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
f"📚 <b>Поиск по регламентам</b>\n\n"
|
||||||
|
f"Введите ваш вопрос (например: <i>'время хранения электронных документов'</i> ).\n"
|
||||||
|
f"Бот найдет наиболее подходящие абзацы из нормативных документов.\n\n"
|
||||||
|
f"Для возврата в главное меню отправьте {emojis['0']}."
|
||||||
|
)
|
||||||
|
|
||||||
|
def _search_unknown(emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
f"⚠️ <b>Неизвестная команда.</b>\n"
|
||||||
|
f"Пожалуйста, используйте цифры меню из сообщения выше.\n\n"
|
||||||
|
f"{emojis['9']} — Назад\n"
|
||||||
|
f"{emojis['0']} — В главное меню"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _search_searching() -> str:
|
||||||
|
return "🔍 <b>Ищу информацию в регламентах...</b>"
|
||||||
|
|
||||||
|
def _search_not_found(emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
f"🤖 Точной информации в регламентах не найдено.\n\n"
|
||||||
|
f"Для выхода отправьте {emojis['0']}."
|
||||||
|
)
|
||||||
|
|
||||||
|
def _search_feedback_thanks(emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
f"🙏 <b>Спасибо за ваш отзыв!</b> Мы передали его разработчикам.\n\n"
|
||||||
|
f"🔍 <b>Можете сразу задать следующий вопрос по регламентам.</b>\n\n"
|
||||||
|
f"Для выхода отправьте {emojis['0']}."
|
||||||
|
)
|
||||||
|
|
||||||
|
def _search_feedback_info(emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
f"⚠️ <b>Информация передана.</b>\n\n"
|
||||||
|
f"Если желаете, напишите прямо сейчас текстовым сообщением, что конкретно вам не понравилось в ответе.\n\n"
|
||||||
|
f"{emojis['1']} — Задать ещё вопрос\n"
|
||||||
|
f"{emojis['0']} — Выйти в главное меню"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _search_unavailable(emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
"⚠️ <i>В данный момент выполнить это действие невозможно. "
|
||||||
|
"Информация о проблеме передана ответственным.\n"
|
||||||
|
"Приносим извинения за доставленные неудобства.</i>\n\n"
|
||||||
|
f"<i>{emojis['0']} — В главное меню</i>"
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Public constants and functions ---
|
||||||
|
|
||||||
|
SEARCH_MAIN_MENU_TEXT = _search_welcome(EMOJI_DIGITS)
|
||||||
|
SEARCH_UNKNOWN_CMD_TEXT = _search_unknown(EMOJI_DIGITS)
|
||||||
|
|
||||||
|
def search_searching_text() -> str:
|
||||||
|
return _search_searching()
|
||||||
|
|
||||||
|
def search_not_found_text(emojis: dict) -> str:
|
||||||
|
return _search_not_found(emojis)
|
||||||
|
|
||||||
|
def search_feedback_thanks_text(emojis: dict) -> str:
|
||||||
|
return _search_feedback_thanks(emojis)
|
||||||
|
|
||||||
|
def search_feedback_info_text(emojis: dict) -> str:
|
||||||
|
return _search_feedback_info(emojis)
|
||||||
|
|
||||||
|
def search_unavailable_action(emojis: dict) -> str:
|
||||||
|
return _search_unavailable(emojis)
|
||||||
|
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# LK BOT
|
||||||
|
# =========================================================
|
||||||
|
# Модуль Личного Кабинета (финансовые документы, табель и др.)
|
||||||
|
|
||||||
|
# --- Constants ---
|
||||||
|
|
||||||
|
DOC_TYPES = {
|
||||||
|
"1": "Справка о доходах физического лица (2-НДФЛ)",
|
||||||
|
"2": "Справка об удержаниях за ДМС",
|
||||||
|
"3": "Справка о средней заработной плате",
|
||||||
|
"4": "Справка о получении единовременного пособия по рождению ребенка",
|
||||||
|
"5": "Справка о получении ежемесячного пособия и назначении отпуска по уходу за ребенком",
|
||||||
|
"6": "Справка об удержаниях по исполнительным листам",
|
||||||
|
"7": "Копия трудовой книжки"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Private functions (Internal logic) ---
|
||||||
|
|
||||||
|
def _lk_error_handling(emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
"⚠️ <i>В данный момент выполнить это действие невозможно. Информация о проблеме передана ответственным. Приносим извинения за доставленные неудобства.</i>\n\n"
|
||||||
|
f"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _lk_auth_processing(emojis: dict) -> str:
|
||||||
|
return "⏳ <i>Проверка безопасности... Запрашиваю контакты авторизации...</i>"
|
||||||
|
|
||||||
|
def _lk_auth_success(emojis: dict) -> str:
|
||||||
|
return "⏳ <i>Авторизация успешна. Загружаю детализацию расчетного листа...</i>"
|
||||||
|
|
||||||
|
def _lk_auth_2fa_expired(emojis: dict) -> str:
|
||||||
|
return f"⏱ <b>Время действия кода подтверждения изменилось или истекло.</b>\nПожалуйста, запросите расчетный лист заново.{emojis['9']} — Назад"
|
||||||
|
|
||||||
|
def _lk_auth_2fa_wrong_code(emojis: dict, attempts_left: int) -> str:
|
||||||
|
return (
|
||||||
|
f"⚠️ <b>Неверный код подтверждения.</b>\nОсталось попыток: {attempts_left}. Попробуйте еще раз или вернитесь назад.\n\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _lk_auth_2fa_blocked_max(emojis: dict, minutes: int) -> str:
|
||||||
|
return (
|
||||||
|
f"❌ <b>Превышено максимальное количество попыток ввода кода.</b>\nДоступ к финансовым документам заблокирован на <b>{minutes} минут</b>.<br>\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _lk_payslip_loading(emojis: dict) -> str:
|
||||||
|
return "⏳ <i>Формирую архив расчетных листков...</i>"
|
||||||
|
|
||||||
|
def _lk_vacation_loading(emojis: dict) -> str:
|
||||||
|
return "⏳ <i>Формирую справку по отпускам...</i>"
|
||||||
|
|
||||||
|
def _lk_sick_loading(emojis: dict) -> str:
|
||||||
|
return "⏳ <i>Загружаю историю больничных листов...</i>"
|
||||||
|
|
||||||
|
def _lk_missions_loading(emojis: dict) -> str:
|
||||||
|
return "⏳ <i>Загружаю историю командировок...</i>"
|
||||||
|
|
||||||
|
def _lk_rewards_loading(emojis: dict) -> str:
|
||||||
|
return "⏳ <i>Ищу информацию о ваших наградах...</i>"
|
||||||
|
|
||||||
|
def _lk_transfers_loading(emojis: dict) -> str:
|
||||||
|
return "⏳ <i>Загружаю историю кадровых перемещений...</i>"
|
||||||
|
|
||||||
|
def _lk_tabel_loading(emojis: dict) -> str:
|
||||||
|
return "⏳ <i>Рассчитываю баланс рабочего времени...</i>"
|
||||||
|
|
||||||
|
def _lk_auth_sending(emojis: dict) -> str:
|
||||||
|
return "⏳ <i>Отправка заявки...</i>"
|
||||||
|
|
||||||
|
def _lk_success_submission(emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
"✅ <b>Ваша заявка успешно отправлена!</b>\n"
|
||||||
|
"Специалисты свяжутся с вами по готовности документа.\n\n"
|
||||||
|
f"<i>{emojis['9']} — Назад в список справок\n{emojis['0']} — В главное меню</i>"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _lk_main_menu_text(header: str, emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
f"{header}\n"
|
||||||
|
f"{emojis['1']} — Расчётный лист\n"
|
||||||
|
f"{emojis['2']} — Информация о днях отпуска\n"
|
||||||
|
f"{emojis['3']} — Больничные\n"
|
||||||
|
f"{emojis['4']} — Командировки\n"
|
||||||
|
f"{emojis['5']} — Награды\n"
|
||||||
|
f"{emojis['6']} — Переводы\n"
|
||||||
|
f"{emojis['7']} — Табель учёта рабочего времени\n"
|
||||||
|
f"{emojis['8']} — Справки\n\n"
|
||||||
|
f"<i>{emojis['0']} — Вернуться в главное меню</i>"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _lk_tabel_months_menu_text(prev_1, prev_2, months_ru, emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
"⏱ <b>Выберите месяц для просмотра табеля:</b>\n\n"
|
||||||
|
f"{emojis['1']} — {months_ru[prev_1.month]} {prev_1.year} г.\n"
|
||||||
|
f"{emojis['2']} — {months_ru[prev_2.month]} {prev_2.year} г.\n\n"
|
||||||
|
f"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _lk_payslip_months_menu_text(months_data: list, months_ru: dict, emojis: dict) -> str:
|
||||||
|
menu_text = "📅 <b>Выберите месяц для просмотра:</b>\n\n"
|
||||||
|
for idx, cmonth in enumerate(months_data, start=1):
|
||||||
|
month_name = months_ru.get((cmonth - 1) % 12 + 1, '00')
|
||||||
|
year = (cmonth - 1) // 12
|
||||||
|
menu_text += f"{emojis[str(idx)]} — {month_name} {year} г.\n"
|
||||||
|
menu_text += f"\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
return menu_text
|
||||||
|
|
||||||
|
def _lk_docs_quantity_prompt(emojis: dict) -> str:
|
||||||
|
return f"Укажите необходимое количество экземпляров (максимум — 5 шт.):\n\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
|
||||||
|
def _lk_docs_year_prompt(emojis: dict) -> str:
|
||||||
|
return f"📅 <b>Выберите год, за который требуется справка:</b>\n\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
|
||||||
|
def _lk_years_menu(current_year: int, emojis: dict) -> str:
|
||||||
|
lines = ["📅 <b>Выберите год запрашиваемого периода:</b>\n"]
|
||||||
|
for i in range(1, 9):
|
||||||
|
year = current_year - (i - 1)
|
||||||
|
emoji = emojis.get(str(i), f"{i}⃣")
|
||||||
|
lines.append(f"{emoji} — {year} год")
|
||||||
|
lines.append(f"\n<i>{emojis.get('9', '9⃣')} — Назад\n{emojis.get('0', '0⃣')} — В главное меню</i>")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def _lk_docs_avg_salary_prompt(emojis: dict) -> str:
|
||||||
|
return f"Укажите количество месяцев для расчета средней ЗП (например: 3):\n\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
|
||||||
|
def _lk_docs_child_age_prompt(emojis: dict) -> str:
|
||||||
|
return f"👶 <b>Выберите возраст ребенка (до скольки лет):</b>\n\n{emojis['1']} — до 1.5 лет\n{emojis['2']} — до 3 лет\n\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
|
||||||
|
def _lk_docs_period_prompt(year: int, emojis: dict) -> str:
|
||||||
|
return f"за {year} год\n\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
|
||||||
|
def _lk_docs_child_age_options(emojis: dict) -> str:
|
||||||
|
return f"{emojis['1']} — до 1.5 лет\n{emojis['2']} — до 3 лет\n\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
|
||||||
|
def _lk_docs_period_options(emojis: dict) -> str:
|
||||||
|
return f"{emojis['1']} — до 1.5 лет\n{emojis['2']} — до 3 лет\n\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
|
||||||
|
def _lk_nav_text(emojis: dict) -> str:
|
||||||
|
return f"\n\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
|
||||||
|
# --- Public constants and functions ---
|
||||||
|
|
||||||
|
LK_DOCS_MENU_TEXT = (
|
||||||
|
"\n".join([f"{EMOJI_DIGITS[k]} — {v}" for k, v in DOC_TYPES.items()]) +
|
||||||
|
f"\n\n<i>{EMOJI_DIGITS['9']} — Назад\n{EMOJI_DIGITS['0']} — В главное меню</i>"
|
||||||
|
)
|
||||||
|
LK_UNKNOWN_CMD_TEXT = (
|
||||||
|
"⚠️ <b>Неизвестная команда.</b>\n"
|
||||||
|
"Пожалуйста, выберите пункт меню, используя цифры или кнопки.<br>\n"
|
||||||
|
f"<i>{EMOJI_DIGITS['9']} — Назад\n{EMOJI_DIGITS['0']} — В главное меню</i>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def lk_error_handling(emojis: dict) -> str:
|
||||||
|
return _lk_error_handling(emojis)
|
||||||
|
|
||||||
|
def lk_auth_processing(emojis: dict) -> str:
|
||||||
|
return _lk_auth_processing(emojis)
|
||||||
|
|
||||||
|
def lk_auth_success(emojis: dict) -> str:
|
||||||
|
return _lk_auth_success(emojis)
|
||||||
|
|
||||||
|
def lk_auth_2fa_expired(emojis: dict) -> str:
|
||||||
|
return _lk_auth_2fa_expired(emojis)
|
||||||
|
|
||||||
|
def lk_auth_2fa_wrong_code(emojis: dict, attempts_left: int) -> str:
|
||||||
|
return _lk_auth_2fa_wrong_code(emojis, attempts_left)
|
||||||
|
|
||||||
|
def lk_auth_2fa_blocked_max(emojis: dict, minutes: int) -> str:
|
||||||
|
return _lk_auth_2fa_blocked_max(emojis, minutes)
|
||||||
|
|
||||||
|
def lk_payslip_loading(emojis: dict) -> str:
|
||||||
|
return _lk_payslip_loading(emojis)
|
||||||
|
|
||||||
|
def lk_vacation_loading(emojis: dict) -> str:
|
||||||
|
return _lk_vacation_loading(emojis)
|
||||||
|
|
||||||
|
def lk_sick_loading(emojis: dict) -> str:
|
||||||
|
return _lk_sick_loading(emojis)
|
||||||
|
|
||||||
|
def lk_missions_loading(emojis: dict) -> str:
|
||||||
|
return _lk_missions_loading(emojis)
|
||||||
|
|
||||||
|
def lk_rewards_loading(emojis: dict) -> str:
|
||||||
|
return _lk_rewards_loading(emojis)
|
||||||
|
|
||||||
|
def lk_transfers_loading(emojis: dict) -> str:
|
||||||
|
return _lk_transfers_loading(emojis)
|
||||||
|
|
||||||
|
def lk_tabel_loading(emojis: dict) -> str:
|
||||||
|
return _lk_tabel_loading(emojis)
|
||||||
|
|
||||||
|
def lk_auth_sending(emojis: dict) -> str:
|
||||||
|
return _lk_auth_sending(emojis)
|
||||||
|
|
||||||
|
def lk_success_submission(emojis: dict) -> str:
|
||||||
|
return _lk_success_submission(emojis)
|
||||||
|
|
||||||
|
def lk_main_menu_text(header: str, emojis: dict) -> str:
|
||||||
|
return _lk_main_menu_text(header, emojis)
|
||||||
|
|
||||||
|
def lk_tabel_months_menu_text(prev_1, prev_2, months_ru, emojis: dict) -> str:
|
||||||
|
return _lk_tabel_months_menu_text(prev_1, prev_2, months_ru, emojis)
|
||||||
|
|
||||||
|
def lk_payslip_months_menu_text(months_data: list, months_ru: dict, emojis: dict) -> str:
|
||||||
|
return _lk_payslip_months_menu_text(months_data, months_ru, emojis)
|
||||||
|
|
||||||
|
def lk_docs_quantity_prompt(emojis: dict) -> str:
|
||||||
|
return _lk_docs_quantity_prompt(emojis)
|
||||||
|
|
||||||
|
def lk_docs_year_prompt(emojis: dict) -> str:
|
||||||
|
return _lk_docs_year_prompt(emojis)
|
||||||
|
|
||||||
|
def lk_docs_avg_salary_prompt(emojis: dict) -> str:
|
||||||
|
return _lk_docs_avg_salary_prompt(emojis)
|
||||||
|
|
||||||
|
def lk_docs_child_age_prompt(emojis: dict) -> str:
|
||||||
|
return _lk_docs_child_age_prompt(emojis)
|
||||||
|
|
||||||
|
def lk_docs_period_prompt(year: int, emojis: dict) -> str:
|
||||||
|
return _lk_docs_period_prompt(year, emojis)
|
||||||
|
|
||||||
|
def lk_docs_child_age_options(emojis: dict) -> str:
|
||||||
|
return _lk_docs_child_age_options(emojis)
|
||||||
|
|
||||||
|
def lk_docs_period_options(emojis: dict) -> str:
|
||||||
|
return _lk_docs_period_options(emojis)
|
||||||
|
|
||||||
|
def lk_nav_text(emojis: dict) -> str:
|
||||||
|
return _lk_nav_text(emojis)
|
||||||
|
|
||||||
|
def lk_years_menu(current_year: int, emojis: dict) -> str:
|
||||||
|
return _lk_years_menu(current_year, emojis)
|
||||||
|
|
||||||
|
|
||||||
|
# =========================================================
|
||||||
|
# INSTRUCT BOT (Инструкции)
|
||||||
|
# =========================================================
|
||||||
|
# Модуль инструкций
|
||||||
|
|
||||||
|
# --- Private functions (Internal logic) ---
|
||||||
|
|
||||||
|
def _instruct_main_menu(emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
f"📋 <b>Выберите инструкцию:</b>\n\n"
|
||||||
|
f"{emojis['1']} — 🖥 Trueconf\n"
|
||||||
|
f"{emojis['2']} — 📧 Почта\n\n"
|
||||||
|
f"<i>{emojis['0']} — В главное меню</i>"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _instruct_trueconf(emojis: dict) -> str:
|
||||||
|
"""Статический текст Trueconf — без проверки группы."""
|
||||||
|
return (
|
||||||
|
"🖥 <b>Инструкция по Trueconf</b>\n\n"
|
||||||
|
"Данная функция находится в разработке.\n\n"
|
||||||
|
f"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _instruct_trueconf_with_ad(emojis: dict, cn: str, ticket_id: str = None) -> str:
|
||||||
|
"""
|
||||||
|
Текст Trueconf с проверкой группы 2FA.
|
||||||
|
cn — CN пользователя из AD.
|
||||||
|
ticket_id — номер созданной заявки в SD.
|
||||||
|
"""
|
||||||
|
from utils.ad_checker import check_group_membership
|
||||||
|
|
||||||
|
result = check_group_membership(cn, "2FA")
|
||||||
|
|
||||||
|
if not result["found"]:
|
||||||
|
ticket_part = f"\n\n🎫 <b>Заявка #{ticket_id} создана!</b>" if ticket_id else ""
|
||||||
|
return (
|
||||||
|
f"🖥 <b>Инструкция по Trueconf</b>\n\n"
|
||||||
|
f"Пользователь {cn} не найден в Active Directory.{ticket_part}\n\n"
|
||||||
|
f"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
)
|
||||||
|
|
||||||
|
if result["in_group"]:
|
||||||
|
body = (
|
||||||
|
f"🖥 <b>Инструкция по Trueconf</b>\n\n"
|
||||||
|
f"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
body = (
|
||||||
|
f"ℹ <b>Доступ к Trueconf пока не настроен.</b>\n\n"
|
||||||
|
f"🎫 Заявка #23651 для настройки создана в ServiceDesk!\n\n"
|
||||||
|
f"🔄 После выполнения заявки запросите инструкцию ещё раз.\n\n"
|
||||||
|
f"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
)
|
||||||
|
|
||||||
|
return body
|
||||||
|
|
||||||
|
|
||||||
|
def _instruct_email(emojis: dict) -> str:
|
||||||
|
return (
|
||||||
|
"📧 <b>Инструкция по Почте</b>\n\n"
|
||||||
|
"Данная функция находится в разработке.\n\n"
|
||||||
|
f"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Public constants and functions ---
|
||||||
|
|
||||||
|
INSTRUCT_MAIN_MENU_TEXT = _instruct_main_menu(EMOJI_DIGITS)
|
||||||
|
INSTRUCT_TRUECONF_TEXT = _instruct_trueconf(EMOJI_DIGITS)
|
||||||
|
INSTRUCT_TRUECONF_WITH_AD = _instruct_trueconf_with_ad
|
||||||
|
INSTRUCT_EMAIL_TEXT = _instruct_email(EMOJI_DIGITS)
|
||||||
Reference in New Issue
Block a user