146 lines
8.3 KiB
Python
146 lines
8.3 KiB
Python
from io import BytesIO
|
|
from pathlib import Path
|
|
from xml.sax.saxutils import escape
|
|
|
|
from reportlab.lib import colors
|
|
from reportlab.lib.enums import TA_RIGHT
|
|
from reportlab.lib.pagesizes import A4
|
|
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
|
|
from reportlab.lib.units import mm
|
|
from reportlab.pdfbase import pdfmetrics
|
|
from reportlab.pdfbase.ttfonts import TTFont
|
|
from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle
|
|
|
|
|
|
def _font_names():
|
|
candidates = (
|
|
(Path("C:/Windows/Fonts/arial.ttf"), Path("C:/Windows/Fonts/arialbd.ttf")),
|
|
(
|
|
Path("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"),
|
|
Path("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"),
|
|
),
|
|
)
|
|
for regular_path, bold_path in candidates:
|
|
if regular_path.exists() and bold_path.exists():
|
|
pdfmetrics.registerFont(TTFont("TimerrFont", regular_path))
|
|
pdfmetrics.registerFont(TTFont("TimerrFont-Bold", bold_path))
|
|
pdfmetrics.registerFontFamily(
|
|
"TimerrFont",
|
|
normal="TimerrFont",
|
|
bold="TimerrFont-Bold",
|
|
italic="TimerrFont",
|
|
boldItalic="TimerrFont-Bold",
|
|
)
|
|
return "TimerrFont", "TimerrFont-Bold"
|
|
return "Helvetica", "Helvetica-Bold"
|
|
|
|
|
|
def _duration(minutes):
|
|
hours, remainder = divmod(int(minutes), 60)
|
|
return f"{hours} ч {remainder:02d} мин"
|
|
|
|
|
|
def render_company_pdf(report, company):
|
|
output = BytesIO()
|
|
font, bold_font = _font_names()
|
|
styles = getSampleStyleSheet()
|
|
normal = ParagraphStyle("TimerrNormal", parent=styles["Normal"], fontName=font, fontSize=9, leading=12)
|
|
heading = ParagraphStyle("TimerrHeading", parent=styles["Heading1"], fontName=bold_font, fontSize=18, leading=22)
|
|
subheading = ParagraphStyle("TimerrSubheading", parent=styles["Heading2"], fontName=bold_font, fontSize=13, leading=16)
|
|
task_title = ParagraphStyle("TimerrTaskTitle", parent=normal, fontName=bold_font, fontSize=9, leading=11, textColor=colors.HexColor("#172033"))
|
|
task_description = ParagraphStyle("TimerrTaskDescription", parent=normal, fontSize=8, leading=11, textColor=colors.HexColor("#5a6477"))
|
|
total = ParagraphStyle("TimerrTotal", parent=normal, fontSize=12, alignment=TA_RIGHT)
|
|
document = SimpleDocTemplate(output, pagesize=A4, rightMargin=15 * mm, leftMargin=15 * mm, topMargin=15 * mm, bottomMargin=15 * mm)
|
|
story = [
|
|
Paragraph("Отчёт о выполненных работах", heading),
|
|
Spacer(1, 4 * mm),
|
|
Paragraph(f"<b>Компания:</b> {escape(company.name)}", normal),
|
|
Paragraph(f"<b>Исполнитель:</b> {escape(report['user'].get_full_name() or report['user'].username)}", normal),
|
|
Paragraph(f"<b>Период:</b> {report['month']:02d}.{report['year']}", normal),
|
|
Spacer(1, 4 * mm),
|
|
]
|
|
for company_group in report["companies"]:
|
|
for project_group in company_group["projects"].values():
|
|
story.append(Paragraph(escape(project_group["project"].name), subheading))
|
|
rows = [["Дата", "Задача и описание", "Время", "Ставка", "Сумма"]]
|
|
for task in project_group["tasks"]:
|
|
rows.append([
|
|
task.work_date.strftime("%d.%m.%Y"),
|
|
[
|
|
Paragraph(escape(task.title), task_title),
|
|
Spacer(1, 1.5 * mm),
|
|
Paragraph(escape(task.description), task_description),
|
|
],
|
|
_duration(task.duration_minutes), f"{task.hourly_rate} ₽", f"{task.amount} ₽",
|
|
])
|
|
rows.append(["", "Итого по проекту", _duration(project_group["minutes"]), "", f"{project_group['amount']} ₽"])
|
|
table = Table(rows, colWidths=(22 * mm, 78 * mm, 25 * mm, 25 * mm, 25 * mm), repeatRows=1)
|
|
table.setStyle(TableStyle([
|
|
("FONTNAME", (0, 0), (-1, -1), font), ("FONTSIZE", (0, 0), (-1, -1), 8),
|
|
("FONTNAME", (0, 0), (-1, 0), bold_font),
|
|
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#e9edf4")),
|
|
("GRID", (0, 0), (-1, -1), .35, colors.HexColor("#aeb7c5")),
|
|
("VALIGN", (0, 0), (-1, -1), "TOP"), ("FONTNAME", (0, -1), (-1, -1), font),
|
|
("TOPPADDING", (0, 0), (-1, -1), 5), ("BOTTOMPADDING", (0, 0), (-1, -1), 5),
|
|
]))
|
|
story.extend((table, Spacer(1, 5 * mm)))
|
|
story.append(Paragraph(f"Итого: {_duration(company_group['minutes'])}, {company_group['amount']} ₽", total))
|
|
document.build(story)
|
|
return output.getvalue()
|
|
|
|
|
|
def render_user_pdf(report):
|
|
output = BytesIO()
|
|
font, bold_font = _font_names()
|
|
styles = getSampleStyleSheet()
|
|
normal = ParagraphStyle("UserReportNormal", parent=styles["Normal"], fontName=font, fontSize=9, leading=12)
|
|
heading = ParagraphStyle("UserReportHeading", parent=styles["Heading1"], fontName=bold_font, fontSize=18, leading=22)
|
|
company_heading = ParagraphStyle("UserReportCompany", parent=styles["Heading2"], fontName=bold_font, fontSize=15, leading=18, textColor=colors.HexColor("#172033"))
|
|
project_heading = ParagraphStyle("UserReportProject", parent=styles["Heading3"], fontName=bold_font, fontSize=12, leading=15)
|
|
task_title = ParagraphStyle("UserReportTaskTitle", parent=normal, fontName=bold_font, fontSize=9, leading=11)
|
|
task_description = ParagraphStyle("UserReportTaskDescription", parent=normal, fontSize=8, leading=11, textColor=colors.HexColor("#5a6477"))
|
|
total = ParagraphStyle("UserReportTotal", parent=normal, fontName=bold_font, fontSize=11, alignment=TA_RIGHT)
|
|
grand_total = ParagraphStyle("UserReportGrandTotal", parent=total, fontSize=14, leading=18)
|
|
document = SimpleDocTemplate(output, pagesize=A4, rightMargin=15 * mm, leftMargin=15 * mm, topMargin=15 * mm, bottomMargin=15 * mm)
|
|
user_name = report["user"].get_full_name() or report["user"].username
|
|
story = [
|
|
Paragraph("Общий отчёт о выполненных работах", heading),
|
|
Spacer(1, 4 * mm),
|
|
Paragraph(f"<b>Исполнитель:</b> {escape(user_name)}", normal),
|
|
Paragraph(f"<b>Период:</b> {report['month']:02d}.{report['year']}", normal),
|
|
Spacer(1, 5 * mm),
|
|
]
|
|
for company_group in report["companies"]:
|
|
story.append(Paragraph(escape(company_group["company"].name), company_heading))
|
|
for project_group in company_group["projects"].values():
|
|
story.append(Paragraph(escape(project_group["project"].name), project_heading))
|
|
rows = [["Дата", "Задача и описание", "Время", "Ставка", "Сумма"]]
|
|
for task in project_group["tasks"]:
|
|
rows.append([
|
|
task.work_date.strftime("%d.%m.%Y"),
|
|
[
|
|
Paragraph(escape(task.title), task_title),
|
|
Spacer(1, 1.5 * mm),
|
|
Paragraph(escape(task.description), task_description),
|
|
],
|
|
_duration(task.duration_minutes), f"{task.hourly_rate} ₽", f"{task.amount} ₽",
|
|
])
|
|
rows.append(["", "Итого по проекту", _duration(project_group["minutes"]), "", f"{project_group['amount']} ₽"])
|
|
table = Table(rows, colWidths=(22 * mm, 78 * mm, 25 * mm, 25 * mm, 25 * mm), repeatRows=1)
|
|
table.setStyle(TableStyle([
|
|
("FONTNAME", (0, 0), (-1, -1), font), ("FONTSIZE", (0, 0), (-1, -1), 8),
|
|
("FONTNAME", (0, 0), (-1, 0), bold_font),
|
|
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#e9edf4")),
|
|
("GRID", (0, 0), (-1, -1), .35, colors.HexColor("#aeb7c5")),
|
|
("VALIGN", (0, 0), (-1, -1), "TOP"),
|
|
("TOPPADDING", (0, 0), (-1, -1), 5), ("BOTTOMPADDING", (0, 0), (-1, -1), 5),
|
|
]))
|
|
story.extend((table, Spacer(1, 3 * mm)))
|
|
story.extend((
|
|
Paragraph(f"Итого по компании: {_duration(company_group['minutes'])}, {company_group['amount']} ₽", total),
|
|
Spacer(1, 6 * mm),
|
|
))
|
|
story.append(Paragraph(f"Общий итог: {_duration(report['total_minutes'])}, {report['total_amount']} ₽", grand_total))
|
|
document.build(story)
|
|
return output.getvalue()
|