Files
DutyLog/src/dutylog/services/report_service.py
T
2026-03-01 15:06:22 +03:00

224 lines
8.6 KiB
Python

from datetime import date
from io import BytesIO
from openpyxl import Workbook
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
from dutylog.infrastructure.database.repositories.floors_repository import (
FloorsRepository,
)
from dutylog.infrastructure.database.repositories.hours_transactions_repository import (
HoursTransactionsRepository,
)
from dutylog.infrastructure.database.repositories.residents_repository import (
ResidentsRepository,
)
from dutylog.infrastructure.database.repositories.rooms_repository import (
RoomsRepository,
)
class ReportService:
def __init__(
self,
hours_transactions_repository: HoursTransactionsRepository,
residents_repository: ResidentsRepository,
rooms_repository: RoomsRepository,
floors_repository: FloorsRepository,
):
self.hours_transactions_repository = hours_transactions_repository
self.residents_repository = residents_repository
self.rooms_repository = rooms_repository
self.floors_repository = floors_repository
async def generate_period_report(self, start_date: date, end_date: date) -> BytesIO:
transactions = await self.hours_transactions_repository.get_by_period(
start_date, end_date
)
increase_transactions = [
t for t in transactions if t.transaction_type == "increase"
]
decrease_transactions = [
t for t in transactions if t.transaction_type == "decrease"
]
wb = Workbook()
ws = wb.active
if ws is None:
raise ValueError("Failed to create worksheet")
ws.title = "Отчет по начислениям"
header_font = Font(bold=True, size=12, color="FFFFFF")
header_fill = PatternFill(
start_color="4472C4", end_color="4472C4", fill_type="solid"
)
header_alignment = Alignment(horizontal="center", vertical="center")
summary_font = Font(bold=True, size=11)
summary_fill = PatternFill(
start_color="E7E6E6", end_color="E7E6E6", fill_type="solid"
)
border = Border(
left=Side(style="thin"),
right=Side(style="thin"),
top=Side(style="thin"),
bottom=Side(style="thin"),
)
title_text = f"Отчет по начислениям часов за период {start_date.strftime('%d.%m.%Y')} - {end_date.strftime('%d.%m.%Y')}"
ws.cell(row=1, column=1, value=title_text)
ws.merge_cells("A1:E1")
title_cell = ws.cell(row=1, column=1)
title_cell.font = Font(bold=True, size=14)
title_cell.alignment = Alignment(horizontal="center", vertical="center")
ws.row_dimensions[1].height = 25
row_num = 3
ws.cell(row=row_num, column=1, value="НАЧИСЛЕНИЯ")
ws.cell(row=row_num, column=1).font = Font(bold=True, size=12)
ws.merge_cells(f"A{row_num}:E{row_num}")
row_num += 1
headers = ["Дата", "Резидент", "Комната", "Часы", "Примечание"]
for col_num, header in enumerate(headers, 1):
cell = ws.cell(row=row_num, column=col_num)
cell.value = header
cell.font = header_font
cell.fill = header_fill
cell.alignment = header_alignment
cell.border = border
ws.row_dimensions[row_num].height = 20
row_num += 1
total_increase = 0
for transaction in increase_transactions:
resident = await self.residents_repository.get_by_id(
transaction.resident_id
)
if not resident:
continue
room = await self.rooms_repository.get_by_id(resident.room)
room_number = room.number if room else ""
ws.cell(
row=row_num, column=1, value=transaction.created_at.replace(tzinfo=None)
).border = border
ws.cell(row=row_num, column=1).number_format = "DD.MM.YYYY HH:MM"
ws.cell(
row=row_num, column=2, value=resident.real_name or "Без имени"
).border = border
ws.cell(row=row_num, column=3, value=room_number).border = border
ws.cell(row=row_num, column=4, value=transaction.amount).border = border
ws.cell(
row=row_num, column=5, value=transaction.remark or ""
).border = border
ws.cell(row=row_num, column=1).alignment = Alignment(horizontal="center")
ws.cell(row=row_num, column=3).alignment = Alignment(horizontal="center")
ws.cell(row=row_num, column=4).alignment = Alignment(horizontal="center")
total_increase += transaction.amount
row_num += 1
summary_row = row_num
ws.merge_cells(f"A{summary_row}:C{summary_row}")
summary_cell = ws[f"A{summary_row}"]
summary_cell.value = "Начислено часов:"
summary_cell.font = summary_font
summary_cell.fill = summary_fill
summary_cell.alignment = Alignment(horizontal="right", vertical="center")
summary_cell.border = border
ws.cell(row=summary_row, column=4, value=total_increase).font = summary_font
ws.cell(row=summary_row, column=4).fill = summary_fill
ws.cell(row=summary_row, column=4).alignment = Alignment(horizontal="center")
ws.cell(row=summary_row, column=4).border = border
ws.cell(row=summary_row, column=5, value="").fill = summary_fill
ws.cell(row=summary_row, column=5).border = border
row_num += 3
ws.cell(row=row_num, column=1, value="СПИСАНИЯ")
ws.cell(row=row_num, column=1).font = Font(bold=True, size=12)
ws.merge_cells(f"A{row_num}:E{row_num}")
row_num += 1
for col_num, header in enumerate(headers, 1):
cell = ws.cell(row=row_num, column=col_num)
cell.value = header
cell.font = header_font
cell.fill = header_fill
cell.alignment = header_alignment
cell.border = border
ws.row_dimensions[row_num].height = 20
row_num += 1
total_decrease = 0
for transaction in decrease_transactions:
resident = await self.residents_repository.get_by_id(
transaction.resident_id
)
if not resident:
continue
room = await self.rooms_repository.get_by_id(resident.room)
room_number = room.number if room else ""
ws.cell(
row=row_num, column=1, value=transaction.created_at.replace(tzinfo=None)
).border = border
ws.cell(row=row_num, column=1).number_format = "DD.MM.YYYY HH:MM"
ws.cell(
row=row_num, column=2, value=resident.real_name or "Без имени"
).border = border
ws.cell(row=row_num, column=3, value=room_number).border = border
ws.cell(row=row_num, column=4, value=transaction.amount).border = border
ws.cell(
row=row_num, column=5, value=transaction.remark or ""
).border = border
ws.cell(row=row_num, column=1).alignment = Alignment(horizontal="center")
ws.cell(row=row_num, column=3).alignment = Alignment(horizontal="center")
ws.cell(row=row_num, column=4).alignment = Alignment(horizontal="center")
total_decrease += transaction.amount
row_num += 1
summary_row = row_num
ws.merge_cells(f"A{summary_row}:C{summary_row}")
summary_cell = ws[f"A{summary_row}"]
summary_cell.value = "Списано часов:"
summary_cell.font = summary_font
summary_cell.fill = summary_fill
summary_cell.alignment = Alignment(horizontal="right", vertical="center")
summary_cell.border = border
ws.cell(row=summary_row, column=4, value=total_decrease).font = summary_font
ws.cell(row=summary_row, column=4).fill = summary_fill
ws.cell(row=summary_row, column=4).alignment = Alignment(horizontal="center")
ws.cell(row=summary_row, column=4).border = border
ws.cell(row=summary_row, column=5, value="").fill = summary_fill
ws.cell(row=summary_row, column=5).border = border
ws.column_dimensions["A"].width = 18
ws.column_dimensions["B"].width = 25
ws.column_dimensions["C"].width = 10
ws.column_dimensions["D"].width = 10
ws.column_dimensions["E"].width = 30
output = BytesIO()
wb.save(output)
output.seek(0)
return output