diff --git a/alembic/versions/8861cafdae4f_add_remark_to_hours_transaction_and_.py b/alembic/versions/8861cafdae4f_add_remark_to_hours_transaction_and_.py new file mode 100644 index 0000000..3a1227f --- /dev/null +++ b/alembic/versions/8861cafdae4f_add_remark_to_hours_transaction_and_.py @@ -0,0 +1,39 @@ +"""add_remark_to_hours_transaction_and_reporting_period_model + +Revision ID: 8861cafdae4f +Revises: 0f74944ba3bb +Create Date: 2026-03-01 13:51:37.323937 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '8861cafdae4f' +down_revision: Union[str, Sequence[str], None] = '0f74944ba3bb' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('reporting_periods', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('start_date', sa.Date(), nullable=False), + sa.Column('end_date', sa.Date(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.add_column('hours_transactions', sa.Column('remark', sa.String(length=500), nullable=True)) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('hours_transactions', 'remark') + op.drop_table('reporting_periods') + # ### end Alembic commands ### diff --git a/src/dutylog/application/bot/user_dialogs/admin_dialogs/admin_menu_dialog.py b/src/dutylog/application/bot/user_dialogs/admin_dialogs/admin_menu_dialog.py index 111035f..9939650 100644 --- a/src/dutylog/application/bot/user_dialogs/admin_dialogs/admin_menu_dialog.py +++ b/src/dutylog/application/bot/user_dialogs/admin_dialogs/admin_menu_dialog.py @@ -35,11 +35,17 @@ from dutylog.application.bot.user_dialogs.admin_dialogs.rooms_management import create_room_input_window, create_room_confirm_window, ) +from dutylog.application.bot.user_dialogs.admin_dialogs.reporting_period_management import ( + reporting_period_window, + next_period_confirm_window, +) from dutylog.application.bot.user_dialogs.admin_dialogs.hours_management import ( add_hours_select_window, remove_hours_select_window, add_hours_custom_window, remove_hours_custom_window, + add_hours_remark_window, + remove_hours_remark_window, add_hours_confirm_window, remove_hours_confirm_window, ) @@ -59,6 +65,8 @@ admin_menu_dialog = Dialog( remove_hours_select_window, add_hours_custom_window, remove_hours_custom_window, + add_hours_remark_window, + remove_hours_remark_window, add_hours_confirm_window, remove_hours_confirm_window, create_resident_name_window, @@ -75,6 +83,8 @@ admin_menu_dialog = Dialog( create_room_select_floor_window, create_room_input_window, create_room_confirm_window, + reporting_period_window, + next_period_confirm_window, statistics_window, broadcast_window, broadcast_confirm_window, diff --git a/src/dutylog/application/bot/user_dialogs/admin_dialogs/hours_management.py b/src/dutylog/application/bot/user_dialogs/admin_dialogs/hours_management.py index 8145ab3..cd8d744 100644 --- a/src/dutylog/application/bot/user_dialogs/admin_dialogs/hours_management.py +++ b/src/dutylog/application/bot/user_dialogs/admin_dialogs/hours_management.py @@ -50,9 +50,9 @@ async def on_hours_selected( dialog_manager.dialog_data["selected_hours"] = int(item_id) if dialog_manager.current_context().state == AdminMenuSG.add_hours_select: - await dialog_manager.switch_to(AdminMenuSG.add_hours_confirm) + await dialog_manager.switch_to(AdminMenuSG.add_hours_remark) else: - await dialog_manager.switch_to(AdminMenuSG.remove_hours_confirm) + await dialog_manager.switch_to(AdminMenuSG.remove_hours_remark) async def on_custom_hours_click( @@ -84,19 +84,59 @@ async def on_custom_hours_input( dialog_manager.dialog_data["selected_hours"] = hours if dialog_manager.current_context().state == AdminMenuSG.add_hours_custom: - await dialog_manager.switch_to(AdminMenuSG.add_hours_confirm) + await dialog_manager.switch_to(AdminMenuSG.add_hours_remark) else: - await dialog_manager.switch_to(AdminMenuSG.remove_hours_confirm) + await dialog_manager.switch_to(AdminMenuSG.remove_hours_remark) except ValueError: await message.answer("⚠️ Пожалуйста, введите корректное число") +async def on_add_hours_remark_input( + message: Message, + widget: MessageInput, + dialog_manager: DialogManager, +): + if not message.text or len(message.text.strip()) < 1: + await message.answer("⚠️ Пожалуйста, введите причину добавления часов") + return + + dialog_manager.dialog_data["hours_remark"] = message.text.strip() + await dialog_manager.switch_to(AdminMenuSG.add_hours_confirm) + + +async def on_remove_hours_remark_input( + message: Message, + widget: MessageInput, + dialog_manager: DialogManager, +): + if message.text and len(message.text.strip()) > 0: + dialog_manager.dialog_data["hours_remark"] = message.text.strip() + else: + dialog_manager.dialog_data["hours_remark"] = None + + await dialog_manager.switch_to(AdminMenuSG.remove_hours_confirm) + + +async def on_skip_remark( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +): + dialog_manager.dialog_data["hours_remark"] = None + await dialog_manager.switch_to(AdminMenuSG.remove_hours_confirm) + + async def get_hours_confirm_data( dialog_manager: DialogManager, **kwargs, ): hours = dialog_manager.dialog_data.get("selected_hours", 0) - return {"hours": hours} + remark = dialog_manager.dialog_data.get("hours_remark") + + return { + "hours": hours, + "remark": remark if remark else "Не указана", + } @inject @@ -108,6 +148,7 @@ async def on_add_hours_confirm( ): resident_id = dialog_manager.dialog_data.get("selected_resident_id") hours = dialog_manager.dialog_data.get("selected_hours") + remark = dialog_manager.dialog_data.get("hours_remark") admin_id = callback.from_user.id if resident_id and hours: @@ -116,6 +157,7 @@ async def on_add_hours_confirm( amount=hours, admin_id=admin_id, is_active=True, + remark=remark, ) await dialog_manager.switch_to(AdminMenuSG.resident_info) @@ -131,6 +173,7 @@ async def on_remove_hours_confirm( ): resident_id = dialog_manager.dialog_data.get("selected_resident_id") hours = dialog_manager.dialog_data.get("selected_hours") + remark = dialog_manager.dialog_data.get("hours_remark") admin_id = callback.from_user.id if resident_id and hours: @@ -147,6 +190,7 @@ async def on_remove_hours_confirm( resident_id=resident_id, amount=hours, admin_id=admin_id, + remark=remark, ) await dialog_manager.switch_to(AdminMenuSG.resident_info) @@ -234,8 +278,35 @@ remove_hours_custom_window = Window( state=AdminMenuSG.remove_hours_custom, ) +add_hours_remark_window = Window( + Const("
📝 Причина добавления часов
\n\n
Укажите причину добавления часов (обязательно).
"), + MessageInput(on_add_hours_remark_input), + SwitchTo( + Const("◀️ Назад"), + id="back_to_add_hours_select", + state=AdminMenuSG.add_hours_select, + ), + state=AdminMenuSG.add_hours_remark, +) + +remove_hours_remark_window = Window( + Const("
📝 Причина снятия часов
\n\n
Укажите причину снятия часов (необязательно).
"), + MessageInput(on_remove_hours_remark_input), + Button( + Const("⏭ Пропустить"), + id="skip_remark_btn", + on_click=on_skip_remark, + ), + SwitchTo( + Const("◀️ Назад"), + id="back_to_remove_hours_select", + state=AdminMenuSG.remove_hours_select, + ), + state=AdminMenuSG.remove_hours_remark, +) + add_hours_confirm_window = Window( - Format("
Подтверждение
\n\nВы уверены, что хотите добавить {hours} часов?"), + Format("
Подтверждение
\n\nВы уверены, что хотите добавить {hours} часов?\n\nПричина: {remark}"), Row( Button( Const("✅ Да"), @@ -253,7 +324,7 @@ add_hours_confirm_window = Window( ) remove_hours_confirm_window = Window( - Format("
Подтверждение
\n\nВы уверены, что хотите отнять {hours} часов?"), + Format("
Подтверждение
\n\nВы уверены, что хотите отнять {hours} часов?\n\nПричина: {remark}"), Row( Button( Const("✅ Да"), diff --git a/src/dutylog/application/bot/user_dialogs/admin_dialogs/main_menu.py b/src/dutylog/application/bot/user_dialogs/admin_dialogs/main_menu.py index 5de0001..4c75de1 100644 --- a/src/dutylog/application/bot/user_dialogs/admin_dialogs/main_menu.py +++ b/src/dutylog/application/bot/user_dialogs/admin_dialogs/main_menu.py @@ -4,6 +4,7 @@ from aiogram_dialog.widgets.text import Format, Const from aiogram_dialog.widgets.kbd import Row, SwitchTo, Button from dishka import FromDishka from dishka.integrations.aiogram_dialog import inject +from datetime import datetime, timedelta from dutylog.application.bot.user_dialogs.states import AdminMenuSG from dutylog.infrastructure.database.repositories.users_repository import ( @@ -12,13 +13,33 @@ from dutylog.infrastructure.database.repositories.users_repository import ( from dutylog.infrastructure.database.repositories.residents_repository import ( ResidentsRepository, ) +from dutylog.infrastructure.database.repositories.reporting_periods_repository import ( + ReportingPeriodsRepository, +) from dutylog.infrastructure.utils.config import Config +MONTH_NAMES = { + 1: "Январь", + 2: "Февраль", + 3: "Март", + 4: "Апрель", + 5: "Май", + 6: "Июнь", + 7: "Июль", + 8: "Август", + 9: "Сентябрь", + 10: "Октябрь", + 11: "Ноябрь", + 12: "Декабрь", +} + + @inject async def get_admin_menu_data( event_from_user: User, users_repository: FromDishka[UsersRepository], + reporting_periods_repository: FromDishka[ReportingPeriodsRepository], config: FromDishka[Config], **kwargs, ): @@ -36,11 +57,32 @@ async def get_admin_menu_data( else: greeting = "👨‍💼 Администратор" + active_period = await reporting_periods_repository.get_active_period() + + if active_period: + start_date = active_period.start_date + next_day = start_date + timedelta(days=1) + reporting_month = MONTH_NAMES[next_day.month] + reporting_year = next_day.year + + days_passed = (datetime.now().date() - start_date).days + + period_info = f""" +━━━━━━━━━━━━━━━━━━━━ + +📅 Активный отчётный период +
Месяц: {reporting_month} {reporting_year} +Начало: {start_date.strftime('%d.%m.%Y')} +Прошло дней: {days_passed}
+""" + else: + period_info = "" + content = f""" {greeting}
📋 Панель управления
- +{period_info} Выберите действие: """ @@ -91,6 +133,10 @@ async def on_floors_click(callback, button, dialog_manager): await dialog_manager.switch_to(AdminMenuSG.floors) +async def on_reporting_period_click(callback, button, dialog_manager): + await dialog_manager.switch_to(AdminMenuSG.reporting_period) + + main_menu_window = Window( Format("{content}"), SwitchTo( @@ -110,6 +156,11 @@ main_menu_window = Window( on_click=on_floors_click, ), ), + Button( + Const("📅 Отчётный период"), + id="reporting_period_btn", + on_click=on_reporting_period_click, + ), SwitchTo( Const("📊 Статистика"), id="stats_btn", diff --git a/src/dutylog/application/bot/user_dialogs/admin_dialogs/reporting_period_management.py b/src/dutylog/application/bot/user_dialogs/admin_dialogs/reporting_period_management.py new file mode 100644 index 0000000..354767f --- /dev/null +++ b/src/dutylog/application/bot/user_dialogs/admin_dialogs/reporting_period_management.py @@ -0,0 +1,203 @@ +from aiogram.types import CallbackQuery +from aiogram_dialog import Window, DialogManager +from aiogram_dialog.widgets.text import Format, Const +from aiogram_dialog.widgets.kbd import SwitchTo, Button, Row +from dishka import FromDishka +from dishka.integrations.aiogram_dialog import inject +from datetime import datetime, timedelta + +from dutylog.application.bot.user_dialogs.states import AdminMenuSG +from dutylog.infrastructure.database.repositories.reporting_periods_repository import ( + ReportingPeriodsRepository, +) + + +MONTH_NAMES = { + 1: "Январь", + 2: "Февраль", + 3: "Март", + 4: "Апрель", + 5: "Май", + 6: "Июнь", + 7: "Июль", + 8: "Август", + 9: "Сентябрь", + 10: "Октябрь", + 11: "Ноябрь", + 12: "Декабрь", +} + + +async def on_reporting_period_click( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +): + await dialog_manager.switch_to(AdminMenuSG.reporting_period) + + +@inject +async def get_reporting_period_data( + reporting_periods_repository: FromDishka[ReportingPeriodsRepository], + **kwargs, +): + active_period = await reporting_periods_repository.get_active_period() + + if active_period: + start_date = active_period.start_date + next_day = start_date + timedelta(days=1) + reporting_month = MONTH_NAMES[next_day.month] + reporting_year = next_day.year + + days_passed = (datetime.now().date() - start_date).days + + content = f""" +
📅 Отчётный период
+ +Статус: 🟢 Активен + +Отчётный месяц: {reporting_month} {reporting_year} +Дата начала: {start_date.strftime('%d.%m.%Y')} +Прошло дней: {days_passed} + +━━━━━━━━━━━━━━━━━━━━ + +Используйте кнопки ниже для управления периодом. +""" + has_active = True + else: + content = """ +
📅 Отчётный период
+ +Статус: ⚪️ Нет активного периода + +━━━━━━━━━━━━━━━━━━━━ + +Создайте новый отчётный период, чтобы начать учёт дежурств. +""" + has_active = False + + return { + "content": content, + "has_active": has_active, + } + + +async def on_next_period_click( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +): + await dialog_manager.switch_to(AdminMenuSG.next_period_confirm) + + +async def on_make_report_click( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +): + await callback.answer("⚠️ Функционал в разработке", show_alert=True) + + +@inject +async def get_next_period_confirm_data( + reporting_periods_repository: FromDishka[ReportingPeriodsRepository], + **kwargs, +): + active_period = await reporting_periods_repository.get_active_period() + + if active_period: + start_date = active_period.start_date + next_day = start_date + timedelta(days=1) + reporting_month = MONTH_NAMES[next_day.month] + reporting_year = next_day.year + + content = f""" +
⚠️ Подтверждение
+ +Вы уверены, что хотите начать следующий отчётный период? + +Текущий период: +• Месяц: {reporting_month} {reporting_year} +• Начало: {start_date.strftime('%d.%m.%Y')} + +Текущий период будет закрыт с датой окончания {datetime.now().date().strftime('%d.%m.%Y')}, и будет создан новый период. +""" + else: + content = f""" +
⚠️ Подтверждение
+ +Вы уверены, что хотите создать новый отчётный период? + +Период начнётся с {datetime.now().date().strftime('%d.%m.%Y')}. +""" + + return {"content": content} + + +@inject +async def on_next_period_confirm( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, + reporting_periods_repository: FromDishka[ReportingPeriodsRepository], +): + active_period = await reporting_periods_repository.get_active_period() + current_date = datetime.now().date() + + if active_period: + await reporting_periods_repository.close_period(active_period.id, current_date) + + await reporting_periods_repository.create_period(current_date) + + await callback.answer("✅ Новый отчётный период создан!") + await dialog_manager.switch_to(AdminMenuSG.reporting_period) + + +async def on_next_period_cancel( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +): + await dialog_manager.switch_to(AdminMenuSG.reporting_period) + + +reporting_period_window = Window( + Format("{content}"), + Button( + Const("➡️ Следующий период"), + id="next_period_btn", + on_click=on_next_period_click, + ), + Button( + Const("📊 Сделать отчёт"), + id="make_report_btn", + on_click=on_make_report_click, + when="has_active", + ), + SwitchTo( + Const("◀️ Назад"), + id="back_to_admin_menu_from_period", + state=AdminMenuSG.main, + ), + state=AdminMenuSG.reporting_period, + getter=get_reporting_period_data, +) + +next_period_confirm_window = Window( + Format("{content}"), + Row( + Button( + Const("✅ Да"), + id="confirm_next_period", + on_click=on_next_period_confirm, + ), + Button( + Const("❌ Нет"), + id="cancel_next_period", + on_click=on_next_period_cancel, + ), + ), + state=AdminMenuSG.next_period_confirm, + getter=get_next_period_confirm_data, +) diff --git a/src/dutylog/application/bot/user_dialogs/states.py b/src/dutylog/application/bot/user_dialogs/states.py index 9bbce5f..8147281 100644 --- a/src/dutylog/application/bot/user_dialogs/states.py +++ b/src/dutylog/application/bot/user_dialogs/states.py @@ -22,7 +22,9 @@ class AdminMenuSG(StatesGroup): remove_hours_select = State() add_hours_custom = State() remove_hours_custom = State() + add_hours_remark = State() add_hours_confirm = State() + remove_hours_remark = State() remove_hours_confirm = State() create_resident_name = State() create_resident_floor = State() @@ -38,6 +40,8 @@ class AdminMenuSG(StatesGroup): create_room_select_floor = State() create_room_input = State() create_room_confirm = State() + reporting_period = State() + next_period_confirm = State() statistics = State() broadcast = State() broadcast_confirm = State() diff --git a/src/dutylog/infrastructure/database/dao/reporting_periods_dao.py b/src/dutylog/infrastructure/database/dao/reporting_periods_dao.py new file mode 100644 index 0000000..626242e --- /dev/null +++ b/src/dutylog/infrastructure/database/dao/reporting_periods_dao.py @@ -0,0 +1,42 @@ +from sqlalchemy import select, update, delete +from sqlalchemy.ext.asyncio import AsyncSession + +from dutylog.infrastructure.database.models.reporting_period import ReportingPeriod + + +class ReportingPeriodsDAO: + def __init__(self, session: AsyncSession): + self.session = session + + async def get_by_id(self, period_id: int) -> ReportingPeriod | None: + result = await self.session.execute( + select(ReportingPeriod).where(ReportingPeriod.id == period_id) + ) + return result.scalar_one_or_none() + + async def get_all(self) -> list[ReportingPeriod]: + result = await self.session.execute(select(ReportingPeriod)) + return list(result.scalars().all()) + + async def get_active_period(self) -> ReportingPeriod | None: + result = await self.session.execute( + select(ReportingPeriod).where(ReportingPeriod.end_date.is_(None)) + ) + return result.scalar_one_or_none() + + async def create(self, period: ReportingPeriod) -> ReportingPeriod: + self.session.add(period) + await self.session.commit() + await self.session.refresh(period) + return period + + async def update(self, period_id: int, **kwargs) -> ReportingPeriod | None: + await self.session.execute( + update(ReportingPeriod).where(ReportingPeriod.id == period_id).values(**kwargs) + ) + await self.session.commit() + return await self.get_by_id(period_id) + + async def delete(self, period_id: int) -> None: + await self.session.execute(delete(ReportingPeriod).where(ReportingPeriod.id == period_id)) + await self.session.commit() diff --git a/src/dutylog/infrastructure/database/models/__init__.py b/src/dutylog/infrastructure/database/models/__init__.py index a049653..b130e72 100644 --- a/src/dutylog/infrastructure/database/models/__init__.py +++ b/src/dutylog/infrastructure/database/models/__init__.py @@ -4,5 +4,6 @@ from dutylog.infrastructure.database.models.hours_transaction import HoursTransa from dutylog.infrastructure.database.models.room import Room from dutylog.infrastructure.database.models.resident import Resident from dutylog.infrastructure.database.models.floor import Floor +from dutylog.infrastructure.database.models.reporting_period import ReportingPeriod -__all__ = ["Base", "User", "HoursTransaction", "Room", "Resident", "Floor"] +__all__ = ["Base", "User", "HoursTransaction", "Room", "Resident", "Floor", "ReportingPeriod"] diff --git a/src/dutylog/infrastructure/database/models/hours_transaction.py b/src/dutylog/infrastructure/database/models/hours_transaction.py index 3cf7eb4..f9a3df7 100644 --- a/src/dutylog/infrastructure/database/models/hours_transaction.py +++ b/src/dutylog/infrastructure/database/models/hours_transaction.py @@ -25,6 +25,7 @@ class HoursTransaction(Base): admin_id: Mapped[int] = mapped_column( BigInteger, ForeignKey("users.id", ondelete="SET NULL"), nullable=True ) + remark: Mapped[str | None] = mapped_column(String(500), nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=msk_now ) diff --git a/src/dutylog/infrastructure/database/models/reporting_period.py b/src/dutylog/infrastructure/database/models/reporting_period.py new file mode 100644 index 0000000..623e8c6 --- /dev/null +++ b/src/dutylog/infrastructure/database/models/reporting_period.py @@ -0,0 +1,14 @@ +from datetime import date + +from sqlalchemy import Integer, Date +from sqlalchemy.orm import Mapped, mapped_column + +from dutylog.infrastructure.database.models.base import Base + + +class ReportingPeriod(Base): + __tablename__ = "reporting_periods" + + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + start_date: Mapped[date] = mapped_column(Date, nullable=False) + end_date: Mapped[date | None] = mapped_column(Date, nullable=True) diff --git a/src/dutylog/infrastructure/database/repositories/hours_transactions_repository.py b/src/dutylog/infrastructure/database/repositories/hours_transactions_repository.py index d9a2abb..d0be656 100644 --- a/src/dutylog/infrastructure/database/repositories/hours_transactions_repository.py +++ b/src/dutylog/infrastructure/database/repositories/hours_transactions_repository.py @@ -24,12 +24,14 @@ class HoursTransactionsRepository: amount: int, admin_id: int | None = None, is_active: bool = True, + remark: str | None = None, ) -> tuple[HoursTransaction, Resident | None]: transaction = HoursTransaction( resident_id=resident_id, transaction_type=TransactionType.INCREASE.value, amount=amount, admin_id=admin_id, + remark=remark, ) transaction = await self.transactions_dao.create(transaction) @@ -54,12 +56,14 @@ class HoursTransactionsRepository: amount: int, admin_id: int | None = None, is_active: bool = True, + remark: str | None = None, ) -> tuple[HoursTransaction, Resident | None]: transaction = HoursTransaction( resident_id=resident_id, transaction_type=TransactionType.DECREASE.value, amount=amount, admin_id=admin_id, + remark=remark, ) transaction = await self.transactions_dao.create(transaction) @@ -83,6 +87,7 @@ class HoursTransactionsRepository: resident_id: int, amount: int, admin_id: int | None = None, + remark: str | None = None, ) -> tuple[HoursTransaction, Resident | None]: """Перемещает часы из неотработанных в отработанные""" transaction = HoursTransaction( @@ -90,6 +95,7 @@ class HoursTransactionsRepository: transaction_type=TransactionType.DECREASE.value, amount=amount, admin_id=admin_id, + remark=remark, ) transaction = await self.transactions_dao.create(transaction) diff --git a/src/dutylog/infrastructure/database/repositories/reporting_periods_repository.py b/src/dutylog/infrastructure/database/repositories/reporting_periods_repository.py new file mode 100644 index 0000000..7c5bb49 --- /dev/null +++ b/src/dutylog/infrastructure/database/repositories/reporting_periods_repository.py @@ -0,0 +1,28 @@ +from datetime import date + +from dutylog.infrastructure.database.dao.reporting_periods_dao import ReportingPeriodsDAO +from dutylog.infrastructure.database.models.reporting_period import ReportingPeriod + + +class ReportingPeriodsRepository: + def __init__(self, reporting_periods_dao: ReportingPeriodsDAO): + self.reporting_periods_dao = reporting_periods_dao + + async def create_period(self, start_date: date) -> ReportingPeriod: + period = ReportingPeriod(start_date=start_date) + return await self.reporting_periods_dao.create(period) + + async def close_period(self, period_id: int, end_date: date) -> ReportingPeriod | None: + return await self.reporting_periods_dao.update(period_id, end_date=end_date) + + async def get_period_by_id(self, period_id: int) -> ReportingPeriod | None: + return await self.reporting_periods_dao.get_by_id(period_id) + + async def get_all_periods(self) -> list[ReportingPeriod]: + return await self.reporting_periods_dao.get_all() + + async def get_active_period(self) -> ReportingPeriod | None: + return await self.reporting_periods_dao.get_active_period() + + async def delete_period(self, period_id: int) -> None: + await self.reporting_periods_dao.delete(period_id) diff --git a/src/dutylog/infrastructure/ioc.py b/src/dutylog/infrastructure/ioc.py index 868ce65..e001005 100644 --- a/src/dutylog/infrastructure/ioc.py +++ b/src/dutylog/infrastructure/ioc.py @@ -11,6 +11,9 @@ from dutylog.infrastructure.database.dao.hours_transactions_dao import ( from dutylog.infrastructure.database.dao.rooms_dao import RoomsDAO from dutylog.infrastructure.database.dao.residents_dao import ResidentsDAO from dutylog.infrastructure.database.dao.floors_dao import FloorsDAO +from dutylog.infrastructure.database.dao.reporting_periods_dao import ( + ReportingPeriodsDAO, +) from dutylog.infrastructure.database.repositories.users_repository import ( UsersRepository, ) @@ -26,6 +29,9 @@ from dutylog.infrastructure.database.repositories.residents_repository import ( from dutylog.infrastructure.database.repositories.floors_repository import ( FloorsRepository, ) +from dutylog.infrastructure.database.repositories.reporting_periods_repository import ( + ReportingPeriodsRepository, +) from dutylog.infrastructure.utils.config import Config, load_config @@ -75,6 +81,10 @@ class DAOProvider(Provider): def get_floors_dao(self, session: AsyncSession) -> FloorsDAO: return FloorsDAO(session) + @provide(scope=Scope.REQUEST) + def get_reporting_periods_dao(self, session: AsyncSession) -> ReportingPeriodsDAO: + return ReportingPeriodsDAO(session) + class RepositoryProvider(Provider): @provide(scope=Scope.REQUEST) @@ -102,3 +112,9 @@ class RepositoryProvider(Provider): @provide(scope=Scope.REQUEST) def get_floors_repository(self, floors_dao: FloorsDAO) -> FloorsRepository: return FloorsRepository(floors_dao) + + @provide(scope=Scope.REQUEST) + def get_reporting_periods_repository( + self, reporting_periods_dao: ReportingPeriodsDAO + ) -> ReportingPeriodsRepository: + return ReportingPeriodsRepository(reporting_periods_dao)