mirror of
https://github.com/koloideal/DutyLog.git
synced 2026-06-10 10:25:29 +03:00
update
This commit is contained in:
@@ -10,7 +10,7 @@ from dishka.integrations.aiogram import setup_dishka
|
|||||||
|
|
||||||
from dutylog.application.bot.user_handlers import router as user_router
|
from dutylog.application.bot.user_handlers import router as user_router
|
||||||
from dutylog.application.bot.user_dialogs import main_menu_dialog
|
from dutylog.application.bot.user_dialogs import main_menu_dialog
|
||||||
from dutylog.application.bot.user_dialogs.admin_dialogs import admin_menu_dialog
|
from dutylog.application.bot.admin_dialogs import admin_menu_dialog
|
||||||
from dutylog.application.bot.user_dialogs.registration_dialog import registration_dialog
|
from dutylog.application.bot.user_dialogs.registration_dialog import registration_dialog
|
||||||
from dutylog.infrastructure.ioc import (
|
from dutylog.infrastructure.ioc import (
|
||||||
ConfigProvider,
|
ConfigProvider,
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from dutylog.application.bot.admin_dialogs.admin_menu_dialog import (
|
||||||
|
admin_menu_dialog,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = ["admin_menu_dialog"]
|
||||||
+8
-8
@@ -1,10 +1,10 @@
|
|||||||
from aiogram_dialog import Dialog
|
from aiogram_dialog import Dialog
|
||||||
|
|
||||||
from dutylog.application.bot.user_dialogs.admin_dialogs.main_menu import (
|
from dutylog.application.bot.admin_dialogs.main_menu import (
|
||||||
main_menu_window,
|
main_menu_window,
|
||||||
statistics_window,
|
statistics_window,
|
||||||
)
|
)
|
||||||
from dutylog.application.bot.user_dialogs.admin_dialogs.residents_management import (
|
from dutylog.application.bot.admin_dialogs.residents_management import (
|
||||||
residents_list_window,
|
residents_list_window,
|
||||||
resident_info_window,
|
resident_info_window,
|
||||||
resident_logout_confirm_window,
|
resident_logout_confirm_window,
|
||||||
@@ -16,18 +16,18 @@ from dutylog.application.bot.user_dialogs.admin_dialogs.residents_management imp
|
|||||||
search_input_window,
|
search_input_window,
|
||||||
search_results_window,
|
search_results_window,
|
||||||
)
|
)
|
||||||
from dutylog.application.bot.user_dialogs.admin_dialogs.residents_filter import (
|
from dutylog.application.bot.admin_dialogs.residents_filter import (
|
||||||
filter_select_window,
|
filter_select_window,
|
||||||
filter_hours_input_window,
|
filter_hours_input_window,
|
||||||
filtered_results_window,
|
filtered_results_window,
|
||||||
)
|
)
|
||||||
from dutylog.application.bot.user_dialogs.admin_dialogs.floors_management import (
|
from dutylog.application.bot.admin_dialogs.floors_management import (
|
||||||
floors_list_window,
|
floors_list_window,
|
||||||
floor_delete_confirm_window,
|
floor_delete_confirm_window,
|
||||||
create_floor_input_window,
|
create_floor_input_window,
|
||||||
create_floor_confirm_window,
|
create_floor_confirm_window,
|
||||||
)
|
)
|
||||||
from dutylog.application.bot.user_dialogs.admin_dialogs.rooms_management import (
|
from dutylog.application.bot.admin_dialogs.rooms_management import (
|
||||||
rooms_select_floor_window,
|
rooms_select_floor_window,
|
||||||
rooms_list_window,
|
rooms_list_window,
|
||||||
room_delete_confirm_window,
|
room_delete_confirm_window,
|
||||||
@@ -35,13 +35,13 @@ from dutylog.application.bot.user_dialogs.admin_dialogs.rooms_management import
|
|||||||
create_room_input_window,
|
create_room_input_window,
|
||||||
create_room_confirm_window,
|
create_room_confirm_window,
|
||||||
)
|
)
|
||||||
from dutylog.application.bot.user_dialogs.admin_dialogs.reporting_period_management import (
|
from dutylog.application.bot.admin_dialogs.reporting_period_management import (
|
||||||
reporting_period_window,
|
reporting_period_window,
|
||||||
next_period_confirm_window,
|
next_period_confirm_window,
|
||||||
generate_report_select_period_window,
|
generate_report_select_period_window,
|
||||||
generate_report_confirm_window,
|
generate_report_confirm_window,
|
||||||
)
|
)
|
||||||
from dutylog.application.bot.user_dialogs.admin_dialogs.hours_management import (
|
from dutylog.application.bot.admin_dialogs.hours_management import (
|
||||||
add_hours_select_window,
|
add_hours_select_window,
|
||||||
remove_hours_select_window,
|
remove_hours_select_window,
|
||||||
add_hours_custom_window,
|
add_hours_custom_window,
|
||||||
@@ -51,7 +51,7 @@ from dutylog.application.bot.user_dialogs.admin_dialogs.hours_management import
|
|||||||
add_hours_confirm_window,
|
add_hours_confirm_window,
|
||||||
remove_hours_confirm_window,
|
remove_hours_confirm_window,
|
||||||
)
|
)
|
||||||
from dutylog.application.bot.user_dialogs.admin_dialogs.broadcast import (
|
from dutylog.application.bot.admin_dialogs.broadcast import (
|
||||||
broadcast_window,
|
broadcast_window,
|
||||||
broadcast_confirm_window,
|
broadcast_confirm_window,
|
||||||
)
|
)
|
||||||
+13
-14
@@ -35,7 +35,7 @@ async def on_reporting_period_click(
|
|||||||
callback: CallbackQuery,
|
callback: CallbackQuery,
|
||||||
button: Button,
|
button: Button,
|
||||||
dialog_manager: DialogManager,
|
dialog_manager: DialogManager,
|
||||||
):
|
) -> None:
|
||||||
await dialog_manager.switch_to(AdminMenuSG.reporting_period)
|
await dialog_manager.switch_to(AdminMenuSG.reporting_period)
|
||||||
|
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ async def on_reporting_period_click(
|
|||||||
async def get_reporting_period_data(
|
async def get_reporting_period_data(
|
||||||
reporting_periods_repository: FromDishka[ReportingPeriodsRepository],
|
reporting_periods_repository: FromDishka[ReportingPeriodsRepository],
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
) -> dict[str, bool | str]:
|
||||||
active_period = await reporting_periods_repository.get_active_period()
|
active_period = await reporting_periods_repository.get_active_period()
|
||||||
|
|
||||||
if active_period:
|
if active_period:
|
||||||
@@ -90,7 +90,7 @@ async def on_next_period_click(
|
|||||||
callback: CallbackQuery,
|
callback: CallbackQuery,
|
||||||
button: Button,
|
button: Button,
|
||||||
dialog_manager: DialogManager,
|
dialog_manager: DialogManager,
|
||||||
):
|
) -> None:
|
||||||
await dialog_manager.switch_to(AdminMenuSG.next_period_confirm)
|
await dialog_manager.switch_to(AdminMenuSG.next_period_confirm)
|
||||||
|
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ async def on_make_report_click(
|
|||||||
callback: CallbackQuery,
|
callback: CallbackQuery,
|
||||||
button: Button,
|
button: Button,
|
||||||
dialog_manager: DialogManager,
|
dialog_manager: DialogManager,
|
||||||
):
|
) -> None:
|
||||||
await dialog_manager.switch_to(AdminMenuSG.generate_report_select_period)
|
await dialog_manager.switch_to(AdminMenuSG.generate_report_select_period)
|
||||||
|
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ async def on_make_report_click(
|
|||||||
async def get_next_period_confirm_data(
|
async def get_next_period_confirm_data(
|
||||||
reporting_periods_repository: FromDishka[ReportingPeriodsRepository],
|
reporting_periods_repository: FromDishka[ReportingPeriodsRepository],
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
) -> dict[str, str]:
|
||||||
active_period = await reporting_periods_repository.get_active_period()
|
active_period = await reporting_periods_repository.get_active_period()
|
||||||
|
|
||||||
if active_period:
|
if active_period:
|
||||||
@@ -144,7 +144,7 @@ async def on_next_period_confirm(
|
|||||||
button: Button,
|
button: Button,
|
||||||
dialog_manager: DialogManager,
|
dialog_manager: DialogManager,
|
||||||
reporting_periods_repository: FromDishka[ReportingPeriodsRepository],
|
reporting_periods_repository: FromDishka[ReportingPeriodsRepository],
|
||||||
):
|
) -> None:
|
||||||
active_period = await reporting_periods_repository.get_active_period()
|
active_period = await reporting_periods_repository.get_active_period()
|
||||||
current_date = datetime.now().date()
|
current_date = datetime.now().date()
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ async def on_next_period_cancel(
|
|||||||
callback: CallbackQuery,
|
callback: CallbackQuery,
|
||||||
button: Button,
|
button: Button,
|
||||||
dialog_manager: DialogManager,
|
dialog_manager: DialogManager,
|
||||||
):
|
) -> None:
|
||||||
await dialog_manager.switch_to(AdminMenuSG.reporting_period)
|
await dialog_manager.switch_to(AdminMenuSG.reporting_period)
|
||||||
|
|
||||||
|
|
||||||
@@ -209,7 +209,7 @@ next_period_confirm_window = Window(
|
|||||||
async def get_generate_report_data(
|
async def get_generate_report_data(
|
||||||
reporting_periods_repository: FromDishka[ReportingPeriodsRepository],
|
reporting_periods_repository: FromDishka[ReportingPeriodsRepository],
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
) -> dict[str, bool | str | list[tuple[str, int]]]:
|
||||||
all_periods = await reporting_periods_repository.get_all()
|
all_periods = await reporting_periods_repository.get_all()
|
||||||
completed_periods = [p for p in all_periods if p.end_date is not None]
|
completed_periods = [p for p in all_periods if p.end_date is not None]
|
||||||
active_period = await reporting_periods_repository.get_active_period()
|
active_period = await reporting_periods_repository.get_active_period()
|
||||||
@@ -268,7 +268,7 @@ async def on_period_selected(
|
|||||||
widget,
|
widget,
|
||||||
dialog_manager: DialogManager,
|
dialog_manager: DialogManager,
|
||||||
item_id: str,
|
item_id: str,
|
||||||
):
|
) -> None:
|
||||||
dialog_manager.dialog_data["selected_period_id"] = int(item_id)
|
dialog_manager.dialog_data["selected_period_id"] = int(item_id)
|
||||||
await dialog_manager.switch_to(AdminMenuSG.generate_report_confirm)
|
await dialog_manager.switch_to(AdminMenuSG.generate_report_confirm)
|
||||||
|
|
||||||
@@ -278,7 +278,7 @@ async def get_generate_report_confirm_data(
|
|||||||
reporting_periods_repository: FromDishka[ReportingPeriodsRepository],
|
reporting_periods_repository: FromDishka[ReportingPeriodsRepository],
|
||||||
dialog_manager: DialogManager,
|
dialog_manager: DialogManager,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
) -> dict[str, str]:
|
||||||
period_id = dialog_manager.dialog_data.get("selected_period_id")
|
period_id = dialog_manager.dialog_data.get("selected_period_id")
|
||||||
if not period_id:
|
if not period_id:
|
||||||
return {"content": "⚠️ Период не выбран"}
|
return {"content": "⚠️ Период не выбран"}
|
||||||
@@ -321,7 +321,7 @@ async def on_generate_report_confirm(
|
|||||||
dialog_manager: DialogManager,
|
dialog_manager: DialogManager,
|
||||||
reporting_periods_repository: FromDishka[ReportingPeriodsRepository],
|
reporting_periods_repository: FromDishka[ReportingPeriodsRepository],
|
||||||
report_service: FromDishka[ReportService],
|
report_service: FromDishka[ReportService],
|
||||||
):
|
) -> None:
|
||||||
period_id = dialog_manager.dialog_data.get("selected_period_id")
|
period_id = dialog_manager.dialog_data.get("selected_period_id")
|
||||||
if not period_id:
|
if not period_id:
|
||||||
await callback.answer("⚠️ Период не выбран", show_alert=True)
|
await callback.answer("⚠️ Период не выбран", show_alert=True)
|
||||||
@@ -335,8 +335,7 @@ async def on_generate_report_confirm(
|
|||||||
|
|
||||||
await callback.answer("⏳ Генерирую отчёт...")
|
await callback.answer("⏳ Генерирую отчёт...")
|
||||||
|
|
||||||
end_date = period.end_date if period.end_date else msk_now()
|
end_date = period.end_date if period.end_date else msk_now().date()
|
||||||
print(end_date)
|
|
||||||
|
|
||||||
report_file = await report_service.generate_period_report(
|
report_file = await report_service.generate_period_report(
|
||||||
period.start_date, end_date
|
period.start_date, end_date
|
||||||
@@ -362,7 +361,7 @@ async def on_generate_report_cancel(
|
|||||||
callback: CallbackQuery,
|
callback: CallbackQuery,
|
||||||
button: Button,
|
button: Button,
|
||||||
dialog_manager: DialogManager,
|
dialog_manager: DialogManager,
|
||||||
):
|
) -> None:
|
||||||
await dialog_manager.switch_to(AdminMenuSG.generate_report_select_period)
|
await dialog_manager.switch_to(AdminMenuSG.generate_report_select_period)
|
||||||
|
|
||||||
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
from dutylog.application.bot.user_dialogs.admin_dialogs.admin_menu_dialog import (
|
|
||||||
admin_menu_dialog,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = ["admin_menu_dialog"]
|
|
||||||
@@ -39,27 +39,6 @@ class HoursTransactionsDAO:
|
|||||||
delete(HoursTransaction).where(HoursTransaction.id == transaction_id)
|
delete(HoursTransaction).where(HoursTransaction.id == transaction_id)
|
||||||
)
|
)
|
||||||
await self.session.commit()
|
await self.session.commit()
|
||||||
async def delete(self, transaction_id: int) -> None:
|
|
||||||
await self.session.execute(
|
|
||||||
delete(HoursTransaction).where(HoursTransaction.id == transaction_id)
|
|
||||||
)
|
|
||||||
await self.session.commit()
|
|
||||||
|
|
||||||
async def get_by_period(self, start_date, end_date) -> list[HoursTransaction]:
|
|
||||||
"""Получает транзакции за период"""
|
|
||||||
from datetime import datetime, time
|
|
||||||
|
|
||||||
start_datetime = datetime.combine(start_date, time.min)
|
|
||||||
end_datetime = datetime.combine(end_date, time.max)
|
|
||||||
|
|
||||||
result = await self.session.execute(
|
|
||||||
select(HoursTransaction)
|
|
||||||
.where(HoursTransaction.created_at >= start_datetime)
|
|
||||||
.where(HoursTransaction.created_at <= end_datetime)
|
|
||||||
.order_by(HoursTransaction.created_at.asc())
|
|
||||||
)
|
|
||||||
return list(result.scalars().all())
|
|
||||||
|
|
||||||
|
|
||||||
async def get_by_period(self, start_date, end_date) -> list[HoursTransaction]:
|
async def get_by_period(self, start_date, end_date) -> list[HoursTransaction]:
|
||||||
from datetime import datetime, time
|
from datetime import datetime, time
|
||||||
|
|||||||
@@ -129,10 +129,12 @@ class ServiceProvider(Provider):
|
|||||||
residents_repository: ResidentsRepository,
|
residents_repository: ResidentsRepository,
|
||||||
rooms_repository: RoomsRepository,
|
rooms_repository: RoomsRepository,
|
||||||
floors_repository: FloorsRepository,
|
floors_repository: FloorsRepository,
|
||||||
|
users_repository: UsersRepository,
|
||||||
) -> ReportService:
|
) -> ReportService:
|
||||||
return ReportService(
|
return ReportService(
|
||||||
hours_transactions_repository,
|
hours_transactions_repository,
|
||||||
residents_repository,
|
residents_repository,
|
||||||
rooms_repository,
|
rooms_repository,
|
||||||
floors_repository,
|
floors_repository,
|
||||||
|
users_repository,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from datetime import date
|
from datetime import date, timedelta, timezone
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from openpyxl import Workbook
|
from openpyxl import Workbook
|
||||||
@@ -16,6 +16,11 @@ from dutylog.infrastructure.database.repositories.residents_repository import (
|
|||||||
from dutylog.infrastructure.database.repositories.rooms_repository import (
|
from dutylog.infrastructure.database.repositories.rooms_repository import (
|
||||||
RoomsRepository,
|
RoomsRepository,
|
||||||
)
|
)
|
||||||
|
from dutylog.infrastructure.database.repositories.users_repository import (
|
||||||
|
UsersRepository,
|
||||||
|
)
|
||||||
|
|
||||||
|
MSK_TZ = timezone(timedelta(hours=3))
|
||||||
|
|
||||||
|
|
||||||
class ReportService:
|
class ReportService:
|
||||||
@@ -25,11 +30,13 @@ class ReportService:
|
|||||||
residents_repository: ResidentsRepository,
|
residents_repository: ResidentsRepository,
|
||||||
rooms_repository: RoomsRepository,
|
rooms_repository: RoomsRepository,
|
||||||
floors_repository: FloorsRepository,
|
floors_repository: FloorsRepository,
|
||||||
|
users_repository: UsersRepository,
|
||||||
):
|
):
|
||||||
self.hours_transactions_repository = hours_transactions_repository
|
self.hours_transactions_repository = hours_transactions_repository
|
||||||
self.residents_repository = residents_repository
|
self.residents_repository = residents_repository
|
||||||
self.rooms_repository = rooms_repository
|
self.rooms_repository = rooms_repository
|
||||||
self.floors_repository = floors_repository
|
self.floors_repository = floors_repository
|
||||||
|
self.users_repository = users_repository
|
||||||
|
|
||||||
async def generate_period_report(self, start_date: date, end_date: date) -> BytesIO:
|
async def generate_period_report(self, start_date: date, end_date: date) -> BytesIO:
|
||||||
transactions = await self.hours_transactions_repository.get_by_period(
|
transactions = await self.hours_transactions_repository.get_by_period(
|
||||||
@@ -69,7 +76,7 @@ class ReportService:
|
|||||||
|
|
||||||
title_text = f"Отчет по начислениям часов за период {start_date.strftime('%d.%m.%Y')} - {end_date.strftime('%d.%m.%Y')}"
|
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.cell(row=1, column=1, value=title_text)
|
||||||
ws.merge_cells("A1:E1")
|
ws.merge_cells("A1:F1")
|
||||||
title_cell = ws.cell(row=1, column=1)
|
title_cell = ws.cell(row=1, column=1)
|
||||||
title_cell.font = Font(bold=True, size=14)
|
title_cell.font = Font(bold=True, size=14)
|
||||||
title_cell.alignment = Alignment(horizontal="center", vertical="center")
|
title_cell.alignment = Alignment(horizontal="center", vertical="center")
|
||||||
@@ -79,10 +86,10 @@ class ReportService:
|
|||||||
|
|
||||||
ws.cell(row=row_num, column=1, value="НАЧИСЛЕНИЯ")
|
ws.cell(row=row_num, column=1, value="НАЧИСЛЕНИЯ")
|
||||||
ws.cell(row=row_num, column=1).font = Font(bold=True, size=12)
|
ws.cell(row=row_num, column=1).font = Font(bold=True, size=12)
|
||||||
ws.merge_cells(f"A{row_num}:E{row_num}")
|
ws.merge_cells(f"A{row_num}:F{row_num}")
|
||||||
row_num += 1
|
row_num += 1
|
||||||
|
|
||||||
headers = ["Дата", "Резидент", "Комната", "Часы", "Примечание"]
|
headers = ["Дата", "Резидент", "Комната", "Часы", "Админ", "Примечание"]
|
||||||
for col_num, header in enumerate(headers, 1):
|
for col_num, header in enumerate(headers, 1):
|
||||||
cell = ws.cell(row=row_num, column=col_num)
|
cell = ws.cell(row=row_num, column=col_num)
|
||||||
cell.value = header
|
cell.value = header
|
||||||
@@ -105,9 +112,17 @@ class ReportService:
|
|||||||
|
|
||||||
room = await self.rooms_repository.get_by_id(resident.room)
|
room = await self.rooms_repository.get_by_id(resident.room)
|
||||||
room_number = room.number if room else "—"
|
room_number = room.number if room else "—"
|
||||||
|
|
||||||
|
admin_username = "—"
|
||||||
|
if transaction.admin_id:
|
||||||
|
admin = await self.users_repository.get_user_by_id(transaction.admin_id)
|
||||||
|
if admin and admin.username:
|
||||||
|
admin_username = f"@{admin.username}"
|
||||||
|
|
||||||
|
msk_time = transaction.created_at.astimezone(MSK_TZ).replace(tzinfo=None)
|
||||||
|
|
||||||
ws.cell(
|
ws.cell(
|
||||||
row=row_num, column=1, value=transaction.created_at.replace(tzinfo=None)
|
row=row_num, column=1, value=msk_time
|
||||||
).border = border
|
).border = border
|
||||||
ws.cell(row=row_num, column=1).number_format = "DD.MM.YYYY HH:MM"
|
ws.cell(row=row_num, column=1).number_format = "DD.MM.YYYY HH:MM"
|
||||||
ws.cell(
|
ws.cell(
|
||||||
@@ -115,8 +130,9 @@ class ReportService:
|
|||||||
).border = border
|
).border = border
|
||||||
ws.cell(row=row_num, column=3, value=room_number).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=4, value=transaction.amount).border = border
|
||||||
|
ws.cell(row=row_num, column=5, value=admin_username).border = border
|
||||||
ws.cell(
|
ws.cell(
|
||||||
row=row_num, column=5, value=transaction.remark or "—"
|
row=row_num, column=6, value=transaction.remark or "—"
|
||||||
).border = border
|
).border = border
|
||||||
|
|
||||||
ws.cell(row=row_num, column=1).alignment = Alignment(horizontal="center")
|
ws.cell(row=row_num, column=1).alignment = Alignment(horizontal="center")
|
||||||
@@ -142,12 +158,15 @@ class ReportService:
|
|||||||
|
|
||||||
ws.cell(row=summary_row, column=5, value="").fill = summary_fill
|
ws.cell(row=summary_row, column=5, value="").fill = summary_fill
|
||||||
ws.cell(row=summary_row, column=5).border = border
|
ws.cell(row=summary_row, column=5).border = border
|
||||||
|
|
||||||
|
ws.cell(row=summary_row, column=6, value="").fill = summary_fill
|
||||||
|
ws.cell(row=summary_row, column=6).border = border
|
||||||
|
|
||||||
row_num += 3
|
row_num += 3
|
||||||
|
|
||||||
ws.cell(row=row_num, column=1, value="СПИСАНИЯ")
|
ws.cell(row=row_num, column=1, value="СПИСАНИЯ")
|
||||||
ws.cell(row=row_num, column=1).font = Font(bold=True, size=12)
|
ws.cell(row=row_num, column=1).font = Font(bold=True, size=12)
|
||||||
ws.merge_cells(f"A{row_num}:E{row_num}")
|
ws.merge_cells(f"A{row_num}:F{row_num}")
|
||||||
row_num += 1
|
row_num += 1
|
||||||
|
|
||||||
for col_num, header in enumerate(headers, 1):
|
for col_num, header in enumerate(headers, 1):
|
||||||
@@ -172,9 +191,17 @@ class ReportService:
|
|||||||
|
|
||||||
room = await self.rooms_repository.get_by_id(resident.room)
|
room = await self.rooms_repository.get_by_id(resident.room)
|
||||||
room_number = room.number if room else "—"
|
room_number = room.number if room else "—"
|
||||||
|
|
||||||
|
admin_username = "—"
|
||||||
|
if transaction.admin_id:
|
||||||
|
admin = await self.users_repository.get_user_by_id(transaction.admin_id)
|
||||||
|
if admin and admin.username:
|
||||||
|
admin_username = f"@{admin.username}"
|
||||||
|
|
||||||
|
msk_time = transaction.created_at.astimezone(MSK_TZ).replace(tzinfo=None)
|
||||||
|
|
||||||
ws.cell(
|
ws.cell(
|
||||||
row=row_num, column=1, value=transaction.created_at.replace(tzinfo=None)
|
row=row_num, column=1, value=msk_time
|
||||||
).border = border
|
).border = border
|
||||||
ws.cell(row=row_num, column=1).number_format = "DD.MM.YYYY HH:MM"
|
ws.cell(row=row_num, column=1).number_format = "DD.MM.YYYY HH:MM"
|
||||||
ws.cell(
|
ws.cell(
|
||||||
@@ -182,8 +209,9 @@ class ReportService:
|
|||||||
).border = border
|
).border = border
|
||||||
ws.cell(row=row_num, column=3, value=room_number).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=4, value=transaction.amount).border = border
|
||||||
|
ws.cell(row=row_num, column=5, value=admin_username).border = border
|
||||||
ws.cell(
|
ws.cell(
|
||||||
row=row_num, column=5, value=transaction.remark or "—"
|
row=row_num, column=6, value=transaction.remark or "—"
|
||||||
).border = border
|
).border = border
|
||||||
|
|
||||||
ws.cell(row=row_num, column=1).alignment = Alignment(horizontal="center")
|
ws.cell(row=row_num, column=1).alignment = Alignment(horizontal="center")
|
||||||
@@ -209,12 +237,16 @@ class ReportService:
|
|||||||
|
|
||||||
ws.cell(row=summary_row, column=5, value="").fill = summary_fill
|
ws.cell(row=summary_row, column=5, value="").fill = summary_fill
|
||||||
ws.cell(row=summary_row, column=5).border = border
|
ws.cell(row=summary_row, column=5).border = border
|
||||||
|
|
||||||
|
ws.cell(row=summary_row, column=6, value="").fill = summary_fill
|
||||||
|
ws.cell(row=summary_row, column=6).border = border
|
||||||
|
|
||||||
ws.column_dimensions["A"].width = 18
|
ws.column_dimensions["A"].width = 18
|
||||||
ws.column_dimensions["B"].width = 25
|
ws.column_dimensions["B"].width = 25
|
||||||
ws.column_dimensions["C"].width = 10
|
ws.column_dimensions["C"].width = 10
|
||||||
ws.column_dimensions["D"].width = 10
|
ws.column_dimensions["D"].width = 10
|
||||||
ws.column_dimensions["E"].width = 30
|
ws.column_dimensions["E"].width = 15
|
||||||
|
ws.column_dimensions["F"].width = 30
|
||||||
|
|
||||||
output = BytesIO()
|
output = BytesIO()
|
||||||
wb.save(output)
|
wb.save(output)
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,79 @@
|
|||||||
|
import asyncio
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from dutylog.infrastructure.database.config import create_engine, create_session_maker
|
||||||
|
from dutylog.infrastructure.database.dao.hours_transactions_dao import HoursTransactionsDAO
|
||||||
|
from dutylog.infrastructure.database.dao.residents_dao import ResidentsDAO
|
||||||
|
from dutylog.infrastructure.database.dao.rooms_dao import RoomsDAO
|
||||||
|
from dutylog.infrastructure.database.dao.floors_dao import FloorsDAO
|
||||||
|
from dutylog.infrastructure.database.dao.reporting_periods_dao import ReportingPeriodsDAO
|
||||||
|
from dutylog.infrastructure.database.dao.users_dao import UsersDAO
|
||||||
|
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
|
||||||
|
from dutylog.infrastructure.database.repositories.floors_repository import FloorsRepository
|
||||||
|
from dutylog.infrastructure.database.repositories.reporting_periods_repository import (
|
||||||
|
ReportingPeriodsRepository,
|
||||||
|
)
|
||||||
|
from dutylog.infrastructure.database.repositories.users_repository import UsersRepository
|
||||||
|
from dutylog.infrastructure.utils.config import load_config
|
||||||
|
from dutylog.services.report_service import ReportService
|
||||||
|
|
||||||
|
|
||||||
|
async def test_report_generation() -> None:
|
||||||
|
config = load_config()
|
||||||
|
engine = create_engine(config.database.url)
|
||||||
|
session_maker = create_session_maker(engine)
|
||||||
|
|
||||||
|
async with session_maker() as session:
|
||||||
|
hours_transactions_dao = HoursTransactionsDAO(session)
|
||||||
|
residents_dao = ResidentsDAO(session)
|
||||||
|
rooms_dao = RoomsDAO(session)
|
||||||
|
floors_dao = FloorsDAO(session)
|
||||||
|
reporting_periods_dao = ReportingPeriodsDAO(session)
|
||||||
|
users_dao = UsersDAO(session)
|
||||||
|
|
||||||
|
hours_transactions_repository = HoursTransactionsRepository(
|
||||||
|
hours_transactions_dao, residents_dao
|
||||||
|
)
|
||||||
|
residents_repository = ResidentsRepository(residents_dao)
|
||||||
|
rooms_repository = RoomsRepository(rooms_dao)
|
||||||
|
floors_repository = FloorsRepository(floors_dao)
|
||||||
|
reporting_periods_repository = ReportingPeriodsRepository(reporting_periods_dao)
|
||||||
|
users_repository = UsersRepository(users_dao)
|
||||||
|
|
||||||
|
report_service = ReportService(
|
||||||
|
hours_transactions_repository,
|
||||||
|
residents_repository,
|
||||||
|
rooms_repository,
|
||||||
|
floors_repository,
|
||||||
|
users_repository,
|
||||||
|
)
|
||||||
|
|
||||||
|
active_period = await reporting_periods_repository.get_active_period()
|
||||||
|
|
||||||
|
if not active_period:
|
||||||
|
print("⚠️ Нет активного периода")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"📅 Активный период: с {active_period.start_date}")
|
||||||
|
|
||||||
|
end_date = active_period.end_date if active_period.end_date else date.today()
|
||||||
|
|
||||||
|
print(f"📊 Генерирую отчёт за период {active_period.start_date} - {end_date}")
|
||||||
|
|
||||||
|
report_file = await report_service.generate_period_report(
|
||||||
|
active_period.start_date, end_date
|
||||||
|
)
|
||||||
|
|
||||||
|
filename = f"test_report_{active_period.start_date}_{end_date}.xlsx"
|
||||||
|
with open(filename, "wb") as f:
|
||||||
|
f.write(report_file.read())
|
||||||
|
|
||||||
|
print(f"✅ Отчёт сохранён в файл: {filename}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(test_report_generation())
|
||||||
Reference in New Issue
Block a user