# /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""" 1. Начислено 2. Удержано """ 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'{earn_name}{earn_days}{earn_hours}{earn_prc}{earn_period}{earn_summa}' else: earn_html = '' 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'{ded_name}{ded_prc}{ded_period}{ded_summa}' else: ded_html = '' tbody_html += f"{earn_html}{ded_html}" tbody_html += f""" Всего начислено{format_currency(total_earned)} Всего удержано{format_currency(total_withheld)} 3. Доходы в неденежной форме 4. Выплачено """ 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'{nc_name}{nc_period}{nc_summa}' else: nc_html = '' if pay: pay_name = str(pay[0]).strip() pay_period = translate_period(pay[1] or "") pay_summa = format_currency(pay[2]) pay_html = f'{pay_name}{pay_period}{pay_summa}' else: pay_html = '' tbody_html += f"{nc_html}{pay_html}" tbody_html += f""" Всего доходов в неденежной форме{format_currency(total_noncash)} Всего выплачено{format_currency(total_payouts)} Долг за предприятием на начало месяца{format_currency(debt_comp_start)} Долг за предприятием на конец месяца{format_currency(debt_comp_end)} Долг за работником на начало месяца{format_currency(debt_emp_start)} Долг за работником на конец месяца{format_currency(debt_emp_end)} 5. Выплачено денежными средствами{format_currency(net_pay)} """ html_template = f"""

Расчетный лист за {month_name} {target_tyear} г.

Система оплаты: {header_sys}Оклад: {format_currency(header_wage)}
Стандартные вычеты за месяц: {format_currency(header_deduct)}
Облагаемая база по ставке 13%: {format_currency(header_base)}
{tbody_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"💰 Расчетный листок за {month_name} {tyear} г.\n\n1. Начислено\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}: {format_currency(row[5])} руб.\n" response += f"Всего начислено: {format_currency(total_earned)} руб.\n\n2. Удержано\n" for row in deductions_rows: prc_str = f" ({int(row[1])}%)" if row[1] else "" response += f"🔹 {str(row[0]).strip()}{prc_str}: {format_currency(row[3])} руб.\n" response += f"Всего удержано: {format_currency(total_withheld)} руб.\n\n3. Доходы в неденежной форме: {format_currency(total_noncash)} руб.\n\n4. Выплачено\n" for row in payout_rows: response += f"🔹 {str(row[0]).strip()}: {format_currency(row[2])} руб.\n" response += f"Всего выплачено: {format_currency(total_payouts)} руб.\n\nДолг за предприятием на конец месяца: {format_currency(debt_comp_end)} руб.\nДолг за работником на конец месяца: {format_currency(debt_emp_end)} руб.\n\n5. Выплачено денежными средствами: {format_currency(net_pay)} руб." 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