Initial commit: TrueConf Chatbot КЛЕВЕР

This commit is contained in:
2026-07-15 12:00:27 +07:00
commit fc0288d13d
42 changed files with 7818 additions and 0 deletions
+322
View File
@@ -0,0 +1,322 @@
# /opt/trueconf_bot/lk/payslip_service.py
import calendar
import tempfile
from itertools import zip_longest
from weasyprint import HTML
import config.config as config
# Перевод английских системных месяцев в русские наименования для бланка PDF
ENG_TO_RU_MONTHS = {
"January": "Январь", "February": "Февраль", "March": "Март",
"April": "Апрель", "May": "Май", "June": "Июнь",
"July": "Июль", "August": "Август", "September": "Сентябрь",
"October": "Октябрь", "November": "Ноябрь", "December": "Декабрь"
}
def format_currency(summa):
try:
if summa is None:
return "0,00"
f_summa = float(summa)
return f"{f_summa:,.2f}".replace(",", " ").replace(".", ",")
except:
return str(summa)
def translate_period(period_str):
if not period_str:
return ""
for eng, ru in ENG_TO_RU_MONTHS.items():
if eng in period_str:
return period_str.replace(eng, ru)
return period_str
def get_latest_payslip_months(cursor, card_id: str) -> list:
query = """
SELECT DISTINCT TOP 6 (t.TYEAR * 12 + t.TMONTH) as CMONTH
FROM UOV_SELFSERVICE_PR_EMP e
JOIN UOV_SELFSERVICE_PR_CARD c on c.ID = e.CARD_ID
JOIN UOV_SELFSERVICE_SL_YTAX t on t.CARD_ID = e.CARD_ID
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(query, (card_id,))
months = cursor.fetchall()
if not months:
raise Exception("Отсутствуют расчетные периоды.")
return [m[0] for m in months]
def create_payslip_pdf(month_name, target_tyear,
header_sys, header_wage, header_deduct, header_base,
earnings_rows, deductions_rows, noncash_rows, payout_rows,
total_earned, total_withheld, total_noncash, total_payouts,
debt_comp_start, debt_emp_start, debt_comp_end, debt_emp_end, net_pay):
tbody_html = ""
tbody_html += f"""
<tr>
<td colspan="6" class="section-title">1. Начислено</td>
<td colspan="4" class="section-title">2. Удержано</td>
</tr>
"""
for earn, ded in zip_longest(earnings_rows, deductions_rows):
if earn:
earn_name = str(earn[0]).strip()
earn_days = f"{earn[1]:g}".replace('.', ',') if earn[1] else ""
earn_hours = f"{earn[2]:g}".replace('.', ',') if earn[2] else ""
earn_prc = f"{earn[3]:g}" if earn[3] else ""
earn_period = translate_period(earn[4] or "")
earn_summa = format_currency(earn[5])
earn_html = f'<td class="left-align">{earn_name}</td><td class="center-align">{earn_days}</td><td class="center-align">{earn_hours}</td><td class="center-align">{earn_prc}</td><td class="center-align">{earn_period}</td><td>{earn_summa}</td>'
else:
earn_html = '<td style="border-bottom: none; border-left: none;"></td><td style="border-bottom: none;"></td><td style="border-bottom: none;"></td><td style="border-bottom: none;"></td><td style="border-bottom: none;"></td><td style="border-bottom: none; border-right: none;"></td>'
if ded:
ded_name = str(ded[0]).strip()
ded_prc = f"{ded[1]:.0f}" if ded[1] else ""
ded_period = translate_period(ded[2] or "")
ded_summa = format_currency(ded[3])
ded_html = f'<td class="left-align">{ded_name}</td><td class="center-align">{ded_prc}</td><td class="center-align">{ded_period}</td><td>{ded_summa}</td>'
else:
ded_html = '<td style="border-bottom: none; border-left: none; border-right: none;" colspan="4"></td>'
tbody_html += f"<tr>{earn_html}{ded_html}</tr>"
tbody_html += f"""
<tr class="totals">
<td class="left-align" colspan="5">Всего начислено</td><td>{format_currency(total_earned)}</td>
<td class="left-align" colspan="3">Всего удержано</td><td>{format_currency(total_withheld)}</td>
</tr>
<tr>
<td colspan="6" class="section-title">3. Доходы в неденежной форме</td>
<td colspan="4" class="section-title">4. Выплачено</td>
</tr>
"""
for nc, pay in zip_longest(noncash_rows, payout_rows):
if nc:
nc_name = str(nc[0]).strip()
nc_period = translate_period(nc[1] or "")
nc_summa = format_currency(nc[2])
nc_html = f'<td class="left-align" colspan="4">{nc_name}</td><td class="center-align">{nc_period}</td><td>{nc_summa}</td>'
else:
nc_html = '<td style="border-top: none; border-bottom: none; border-left: none;"></td><td style="border-top: none; border-bottom: none;"></td><td style="border-top: none; border-bottom: none;"></td><td style="border-top: none; border-bottom: none;"></td><td style="border-top: none; border-bottom: none;"></td><td style="border-top: none; border-bottom: none; border-right: none;"></td>'
if pay:
pay_name = str(pay[0]).strip()
pay_period = translate_period(pay[1] or "")
pay_summa = format_currency(pay[2])
pay_html = f'<td class="left-align" colspan="2">{pay_name}</td><td class="center-align">{pay_period}</td><td>{pay_summa}</td>'
else:
pay_html = '<td style="border-top: none; border-bottom: none; border-left: none; border-right: none;" colspan="4"></td>'
tbody_html += f"<tr>{nc_html}{pay_html}</tr>"
tbody_html += f"""
<tr class="totals">
<td class="left-align" colspan="5">Всего доходов в неденежной форме</td><td>{format_currency(total_noncash)}</td>
<td class="left-align" colspan="3">Всего выплачено</td><td>{format_currency(total_payouts)}</td>
</tr>
<tr>
<td class="left-align" colspan="5">Долг за предприятием на начало месяца</td><td>{format_currency(debt_comp_start)}</td>
<td class="left-align" colspan="3">Долг за предприятием на конец месяца</td><td>{format_currency(debt_comp_end)}</td>
</tr>
<tr>
<td class="left-align" colspan="5">Долг за работником на начало месяца</td><td>{format_currency(debt_emp_start)}</td>
<td class="left-align" colspan="3">Долг за работником на конец месяца</td><td>{format_currency(debt_emp_end)}</td>
</tr>
<tr class="totals">
<td colspan="6" class="empty-cell"></td>
<td class="left-align section-title" colspan="3"><b>5. Выплачено денежными средствами</b></td><td class="section-title"><b>{format_currency(net_pay)}</b></td>
</tr>
"""
html_template = f"""
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<style>
@page {{ size: A4 landscape; margin: 15mm; background-color: #ffffff; }}
body {{ font-family: Arial, sans-serif; font-size: 11px; color: #333; margin: 0; padding: 0; }}
h2 {{ text-align: center; margin-bottom: 15px; font-size: 16px; font-weight: normal; }}
table {{ width: 100%; border-collapse: collapse; margin-bottom: 10px; }}
th, td {{ border: 1px solid #a0a0a0; padding: 5px 8px; text-align: right; vertical-align: middle; }}
th {{ background-color: #f2f2f2; text-align: center; font-weight: normal; color: #555; }}
.header-table td {{ border: 1px solid #a0a0a0; text-align: left; background-color: #fcfcfc; }}
.left-align {{ text-align: left; }}
.center-align {{ text-align: center; }}
.section-title {{ background-color: #fcfcfc; font-weight: bold; text-align: left; padding-top: 10px; padding-bottom: 10px; }}
.totals {{ background-color: #fcfcfc; font-weight: bold; }}
.empty-cell {{ border: none; }}
</style>
</head>
<body>
<h2><b>Расчетный лист за {month_name} {target_tyear} г.</b></h2>
<table class="header-table" style="margin-bottom: 15px;">
<tr><td style="width: 60%;">Система оплаты: {header_sys}</td><td style="width: 40%;">Оклад: {format_currency(header_wage)}</td></tr>
<tr><td colspan="2">Стандартные вычеты за месяц: {format_currency(header_deduct)}</td></tr>
<tr><td colspan="2">Облагаемая база по ставке 13%: {format_currency(header_base)}</td></tr>
</table>
<table>
<thead>
<tr>
<th style="width: 20%;">Вид</th><th>Дни</th><th>Часы</th><th>%</th><th>Период</th><th>Сумма</th>
<th style="width: 20%;">Вид</th><th>%</th><th>Период</th><th>Сумма</th>
</tr>
</thead>
<tbody>
{tbody_html}
</tbody>
</table>
</body>
</html>
"""
temp_pdf = tempfile.NamedTemporaryFile(delete=False, suffix='.pdf')
HTML(string=html_template).write_pdf(temp_pdf.name)
return temp_pdf.name
def build_payslip_data(cursor, card_id: str, target_cmonth: int) -> tuple:
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)
months_ru = {1: "Январь", 2: "Февраль", 3: "Март", 4: "Апрель", 5: "Май", 6: "Июнь",
7: "Июль", 8: "Август", 9: "Сентябрь", 10: "Октябрь", 11: "Ноябрь", 12: "Декабрь"}
month_name = months_ru.get(tmonth, f"{tmonth:02d}")
# FIRM_ID по c.ID
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 not firm_row:
raise Exception("Не удалось определить FIRM_ID для сотрудника в указанном периоде.")
firm_id = firm_row[0]
# ШАПКА по c.ID
q_header = """
select s.NAME as [NAME], ROUND(t.WAGE, 2), isnull(sum(yt.u21 + yt.u30), 0), ROUND(isnull(sum(yt.n200 + yt.n5100 - yt.u21 - yt.u30 - yt.u42 - yt.u99), 0), 2)
from (select e.ID, e.FIRM_ID, e.CARD_ID from UOV_SELFSERVICE_PR_EMP e join UOV_SELFSERVICE_PR_CARD c on c.ID=e.CARD_ID
where c.ID = %s and e.FIRM_ID=%s group by e.ID, e.FIRM_ID, e.CARD_ID ) as e
join UOV_SELFSERVICE_pr_trans t on t.EMP_ID = e.ID join UOV_SELFSERVICE_hr_syspay s on s.ID = t.SYSPAY_ID
join UOV_SELFSERVICE_sl_ytax yt on yt.CARD_ID = e.CARD_ID and yt.FIRM_ID = e.FIRM_ID
where e.FIRM_ID = %s and yt.tmonth = %s and yt.tyear = %s
and ((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'))
and t.tp_abbr='CUR' group by s.NAME, t.WAGE, t.D_FROM order by t.D_FROM desc
"""
cursor.execute(q_header, (card_id, firm_id, firm_id, tmonth, tyear, *date_params))
h_row = cursor.fetchone()
header_sys = str(h_row[0]) if h_row else "Неизвестная система"
header_wage = float(h_row[1]) if h_row and h_row[1] else 0.0
header_deduct = float(h_row[2]) if h_row and h_row[2] else 0.0
header_base = float(h_row[3]) if h_row and h_row[3] else 0.0
# НАЧИСЛЕНО по c.ID
q_earn = """
select p.NAME AS [NAME], ROUND(l.days, 1), ROUND(l.hours, 1), CAST(l.prc as INT), convert(varchar, i.D_FROM,104)+'-'+convert(varchar, i.D_TO,104), ROUND(l.summa, 2)
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 and e.FIRM_ID=%s group by e.ID, FIRM_ID ) as e
join UOV_SELFSERVICE_sl_lic l on l.EMP_ID = e.ID join UOV_SELFSERVICE_sl_pay p on p.ID = l.PAY_ID join UOV_SELFSERVICE_pr_ill i on i.ID=l.ID_MAIN
join (select MAX(id) as id, EMP_ID from UOV_SELFSERVICE_pr_trans t 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 EMP_ID) tr on tr.EMP_ID = e.ID
where l.t_pay = 1 and l.pay_id not in (264,301,262) and l.n_dirm = 6 and l.cmonth = %s
UNION ALL
select max(p.NAME) AS [NAME], sum(l.days), ROUND(sum(l.hours), 1), CAST(l.prc as INT), (DateName(month, DateAdd(month, l.TMONTH, 0) - 1) +' '+ cast(l.TYEAR as varchar)), ROUND(isnull(sum(l.summa),0), 2)
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 and e.FIRM_ID=%s group by e.ID, FIRM_ID ) as e
join (select MAX(id) as id, EMP_ID from UOV_SELFSERVICE_pr_trans t 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 EMP_ID) tr on tr.EMP_ID = e.ID
join UOV_SELFSERVICE_sl_lic l on l.EMP_ID = e.ID join UOV_SELFSERVICE_sl_pay p on p.ID = l.PAY_ID
where l.t_pay = 1 and l.pay_id not in (264,301,262) and l.n_dirm <> 6 and l.cmonth = %s
group by l.emp_id,l.pay_id,l.tmonth,l.prc, case l.PAY_ID when 17 then l.id else 0 end, l.TYEAR order by 6 DESC
"""
cursor.execute(q_earn, (card_id, firm_id, *date_params, target_cmonth, card_id, firm_id, *date_params, target_cmonth))
earnings_rows = cursor.fetchall()
# УДЕРЖАНО по c.ID
q_deduct = """
select max(p.NAME) AS [NAME], CAST(l.prc as INT), ISNULL(DATENAME(month, DATEADD(month, l.tmonth, 0) - 1) + ' ' + CAST(NULLIF(l.TYEAR, 0) AS varchar), ''), ROUND(isnull(sum(l.summa),0), 2)
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 and e.FIRM_ID=%s group by e.ID, FIRM_ID ) as e
JOIN UOV_SELFSERVICE_sl_lic l ON l.EMP_ID = e.ID JOIN UOV_SELFSERVICE_sl_pay p ON p.ID = l.PAY_ID
join (select MAX(id) as id, EMP_ID from UOV_SELFSERVICE_pr_trans t 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 EMP_ID) tr on tr.EMP_ID = e.ID
where l.t_pay=2 and l.pay_id not in (301,490) and l.pay_id not in (305, 308, 316, 328, 360, 364) and l.cmonth = %s
group by l.emp_id,l.pay_id,l.tmonth,l.prc, l.id, l.TYEAR order by 4 DESC
"""
cursor.execute(q_deduct, (card_id, firm_id, *date_params, target_cmonth))
deductions_rows = cursor.fetchall()
# ДОЛГИ по c.ID
q_debts = """
select l.pay_id, ROUND(isnull(sum(l.summa),0), 2) 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 and e.FIRM_ID=%s group by e.ID, FIRM_ID ) as e
join UOV_SELFSERVICE_sl_lic l on l.EMP_ID = e.id join (select MAX(id) as id, EMP_ID from UOV_SELFSERVICE_pr_trans t 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 EMP_ID) tr on tr.EMP_ID = e.ID
where l.cmonth = %s and l.pay_id in (264, 301, 490) group by l.pay_id
"""
cursor.execute(q_debts, (card_id, firm_id, *date_params, target_cmonth))
debt_rows = cursor.fetchall()
debt_comp_start, debt_emp_start, debt_emp_end = 0.0, 0.0, 0.0
for r in debt_rows:
if r[0] == 264: debt_comp_start = float(r[1])
elif r[0] == 301: debt_emp_start = float(r[1])
elif r[0] == 490: debt_emp_end = float(r[1])
# НЕ ДЕНЕЖНЫЕ по c.ID
q_noncash = """
select t.NAME AS [NAME], (DateName(month, CAST(%s as date)) +' '+ DateName(YEAR, CAST(%s as date))), ROUND(isnull(p.summa, 0), 2)
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 and e.FIRM_ID=%s group by e.ID, FIRM_ID ) as e
join UOV_SELFSERVICE_sl_profit p on p.EMP_ID = e.ID join UOV_SELFSERVICE_sl_tprofit t on t.ID = p.TPROFIT_ID
join (select MAX(id) as id, EMP_ID from UOV_SELFSERVICE_pr_trans tr where ((tr.D_FROM <= %s and tr.D_TO >= %s) or (tr.D_FROM <= %s and tr.D_TO >= %s) or (tr.D_FROM <= %s and tr.D_TO = '1999-12-31')) group by EMP_ID) tr2 on tr2.EMP_ID = e.ID
where p.cmonth = %s and t.id != 9999
"""
cursor.execute(q_noncash, (date_start, date_start, card_id, firm_id, *date_params, target_cmonth))
noncash_rows = cursor.fetchall()
# ВЫПЛАЧЕНО по c.ID
q_payout = """
select p.NAME AS [NAME], (DateName(month, DateAdd(month, l.TMONTH, 0) - 1) +' '+ cast(l.TYEAR as varchar)), ROUND(sum(isnull(l.summa, 0)), 2)
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 and e.FIRM_ID=%s group by e.ID, FIRM_ID ) as e
join UOV_SELFSERVICE_sl_lic l on l.EMP_ID = e.ID join UOV_SELFSERVICE_sl_pay p on p.ID =l.PAY_ID
join (select MAX(id) as id, EMP_ID from UOV_SELFSERVICE_pr_trans tr where ((tr.D_FROM <= %s and tr.D_TO >= %s) or (tr.D_FROM <= %s and tr.D_TO >= %s) or (tr.D_FROM <= %s and tr.D_TO = '1999-12-31')) group by EMP_ID) tr2 on tr2.EMP_ID = e.ID
where l.t_pay = 2 and l.cmonth = %s and l.pay_id in (305, 308, 316, 328, 360, 364) group by p.NAME, l.TMONTH, l.TYEAR
"""
cursor.execute(q_payout, (card_id, firm_id, *date_params, target_cmonth))
payout_rows = cursor.fetchall()
total_earned = sum(float(r[5]) for r in earnings_rows if r[5])
total_withheld = sum(float(r[3]) for r in deductions_rows if r[3])
total_noncash = sum(float(r[2]) for r in noncash_rows if r[2])
total_payouts = sum(float(r[2]) for r in payout_rows if r[2])
# Расчет долга по формуле СибЦема
debt_comp_end = total_earned - total_withheld - total_payouts
net_pay = total_earned - total_withheld
response = f"💰 <b>Расчетный листок за {month_name} {tyear} г.</b>\n\n<b>1. Начислено</b>\n"
for row in earnings_rows:
time_spent = f" ({float(row[1]):g} дн. / {float(row[2]):g} ч.)" if (row[1] or row[2]) else ""
response += f"🔹 {str(row[0]).strip()}{time_spent}: <b>{format_currency(row[5])} руб.</b>\n"
response += f"<b>Всего начислено: {format_currency(total_earned)} руб.</b>\n\n<b>2. Удержано</b>\n"
for row in deductions_rows:
prc_str = f" ({int(row[1])}%)" if row[1] else ""
response += f"🔹 {str(row[0]).strip()}{prc_str}: <b>{format_currency(row[3])} руб.</b>\n"
response += f"<b>Всего удержано: {format_currency(total_withheld)} руб.</b>\n\n<b>3. Доходы в неденежной форме:</b> {format_currency(total_noncash)} руб.\n\n<b>4. Выплачено</b>\n"
for row in payout_rows:
response += f"🔹 {str(row[0]).strip()}: <b>{format_currency(row[2])} руб.</b>\n"
response += f"<b>Всего выплачено: {format_currency(total_payouts)} руб.</b>\n\n<i>Долг за предприятием на конец месяца: {format_currency(debt_comp_end)} руб.</i>\n<i>Долг за работником на конец месяца: {format_currency(debt_emp_end)} руб.</i>\n\n<b>5. Выплачено денежными средствами: {format_currency(net_pay)} руб.</b>"
pdf_path = create_payslip_pdf(
month_name, tyear, header_sys, header_wage, header_deduct, header_base,
earnings_rows, deductions_rows, noncash_rows, payout_rows,
total_earned, total_withheld, total_noncash, total_payouts,
debt_comp_start, debt_emp_start, debt_comp_end, debt_emp_end, net_pay
)
return response, pdf_path