From 47837353088bf1a96406ef3f9415c95f0b17d04f Mon Sep 17 00:00:00 2001 From: kolo Date: Tue, 3 Mar 2026 22:13:53 +0300 Subject: [PATCH] update --- alembic/versions/c7a225e7de2f_add_models.py | 47 +++++++ .../dao/room_hours_transactions_dao.py | 33 +++++ .../database/models/__init__.py | 3 +- .../infrastructure/database/models/room.py | 2 + .../database/models/room_hours_transaction.py | 25 ++++ .../room_hours_transactions_repository.py | 117 ++++++++++++++++++ src/dutylog/infrastructure/ioc.py | 18 +++ 7 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 alembic/versions/c7a225e7de2f_add_models.py create mode 100644 src/dutylog/infrastructure/database/dao/room_hours_transactions_dao.py create mode 100644 src/dutylog/infrastructure/database/models/room_hours_transaction.py create mode 100644 src/dutylog/infrastructure/database/repositories/room_hours_transactions_repository.py diff --git a/alembic/versions/c7a225e7de2f_add_models.py b/alembic/versions/c7a225e7de2f_add_models.py new file mode 100644 index 0000000..53ccb5f --- /dev/null +++ b/alembic/versions/c7a225e7de2f_add_models.py @@ -0,0 +1,47 @@ +"""add models + +Revision ID: c7a225e7de2f +Revises: 8861cafdae4f +Create Date: 2026-03-03 22:13:33.354768 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'c7a225e7de2f' +down_revision: Union[str, Sequence[str], None] = '8861cafdae4f' +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('room_hours_transactions', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('room_id', sa.Integer(), nullable=False), + sa.Column('transaction_type', sa.String(length=50), nullable=False), + sa.Column('amount', sa.Integer(), nullable=False), + sa.Column('admin_id', sa.BigInteger(), nullable=True), + sa.Column('remark', sa.String(length=500), nullable=True), + sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), + sa.ForeignKeyConstraint(['admin_id'], ['users.id'], ondelete='SET NULL'), + sa.ForeignKeyConstraint(['room_id'], ['rooms.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.add_column('rooms', sa.Column('active_hours', sa.Integer(), server_default='0', nullable=False)) + op.add_column('rooms', sa.Column('inactive_hours', sa.Integer(), server_default='0', nullable=False)) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('rooms', 'inactive_hours') + op.drop_column('rooms', 'active_hours') + op.drop_table('room_hours_transactions') + # ### end Alembic commands ### diff --git a/src/dutylog/infrastructure/database/dao/room_hours_transactions_dao.py b/src/dutylog/infrastructure/database/dao/room_hours_transactions_dao.py new file mode 100644 index 0000000..86aebe5 --- /dev/null +++ b/src/dutylog/infrastructure/database/dao/room_hours_transactions_dao.py @@ -0,0 +1,33 @@ +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from dutylog.infrastructure.database.models.room_hours_transaction import RoomHoursTransaction + + +class RoomHoursTransactionsDAO: + def __init__(self, session: AsyncSession): + self.session = session + + async def get_by_id(self, transaction_id: int) -> RoomHoursTransaction | None: + result = await self.session.execute( + select(RoomHoursTransaction).where(RoomHoursTransaction.id == transaction_id) + ) + return result.scalar_one_or_none() + + async def get_by_room_id(self, room_id: int) -> list[RoomHoursTransaction]: + result = await self.session.execute( + select(RoomHoursTransaction) + .where(RoomHoursTransaction.room_id == room_id) + .order_by(RoomHoursTransaction.created_at.desc()) + ) + return list(result.scalars().all()) + + async def get_all(self) -> list[RoomHoursTransaction]: + result = await self.session.execute(select(RoomHoursTransaction)) + return list(result.scalars().all()) + + async def create(self, transaction: RoomHoursTransaction) -> RoomHoursTransaction: + self.session.add(transaction) + await self.session.commit() + await self.session.refresh(transaction) + return transaction diff --git a/src/dutylog/infrastructure/database/models/__init__.py b/src/dutylog/infrastructure/database/models/__init__.py index b130e72..e112194 100644 --- a/src/dutylog/infrastructure/database/models/__init__.py +++ b/src/dutylog/infrastructure/database/models/__init__.py @@ -1,9 +1,10 @@ from dutylog.infrastructure.database.models.base import Base from dutylog.infrastructure.database.models.user import User from dutylog.infrastructure.database.models.hours_transaction import HoursTransaction +from dutylog.infrastructure.database.models.room_hours_transaction import RoomHoursTransaction 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", "ReportingPeriod"] +__all__ = ["Base", "User", "HoursTransaction", "RoomHoursTransaction", "Room", "Resident", "Floor", "ReportingPeriod"] diff --git a/src/dutylog/infrastructure/database/models/room.py b/src/dutylog/infrastructure/database/models/room.py index d19cae6..29f26c2 100644 --- a/src/dutylog/infrastructure/database/models/room.py +++ b/src/dutylog/infrastructure/database/models/room.py @@ -12,3 +12,5 @@ class Room(Base): on_floor: Mapped[int] = mapped_column( Integer, ForeignKey("floors.id", ondelete="CASCADE"), nullable=False ) + active_hours: Mapped[int] = mapped_column(Integer, default=0, server_default="0") + inactive_hours: Mapped[int] = mapped_column(Integer, default=0, server_default="0") diff --git a/src/dutylog/infrastructure/database/models/room_hours_transaction.py b/src/dutylog/infrastructure/database/models/room_hours_transaction.py new file mode 100644 index 0000000..7ca72d2 --- /dev/null +++ b/src/dutylog/infrastructure/database/models/room_hours_transaction.py @@ -0,0 +1,25 @@ +from datetime import datetime + +from sqlalchemy import BigInteger, Integer, String, DateTime, ForeignKey +from sqlalchemy.orm import Mapped, mapped_column + +from dutylog.infrastructure.database.models.base import Base +from dutylog.infrastructure.utils.datetime import msk_now + + +class RoomHoursTransaction(Base): + __tablename__ = "room_hours_transactions" + + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + room_id: Mapped[int] = mapped_column( + Integer, ForeignKey("rooms.id", ondelete="CASCADE"), nullable=False + ) + transaction_type: Mapped[str] = mapped_column(String(50), nullable=False) + amount: Mapped[int] = mapped_column(Integer, nullable=False) + 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/repositories/room_hours_transactions_repository.py b/src/dutylog/infrastructure/database/repositories/room_hours_transactions_repository.py new file mode 100644 index 0000000..52c4aff --- /dev/null +++ b/src/dutylog/infrastructure/database/repositories/room_hours_transactions_repository.py @@ -0,0 +1,117 @@ +from dutylog.infrastructure.database.dao.room_hours_transactions_dao import ( + RoomHoursTransactionsDAO, +) +from dutylog.infrastructure.database.dao.rooms_dao import RoomsDAO +from dutylog.infrastructure.database.models.room_hours_transaction import ( + RoomHoursTransaction, +) +from dutylog.infrastructure.database.models.hours_transaction import TransactionType +from dutylog.infrastructure.database.models.room import Room + + +class RoomHoursTransactionsRepository: + def __init__( + self, + transactions_dao: RoomHoursTransactionsDAO, + rooms_dao: RoomsDAO, + ): + self.transactions_dao = transactions_dao + self.rooms_dao = rooms_dao + + async def add_hours( + self, + room_id: int, + amount: int, + admin_id: int | None = None, + is_active: bool = True, + ) -> tuple[RoomHoursTransaction, Room | None]: + transaction = RoomHoursTransaction( + room_id=room_id, + transaction_type=TransactionType.INCREASE.value, + amount=amount, + admin_id=admin_id, + ) + transaction = await self.transactions_dao.create(transaction) + + room = await self.rooms_dao.get_by_id(room_id) + if room: + if is_active: + new_hours = room.active_hours + amount + room = await self.rooms_dao.update( + room_id, active_hours=new_hours + ) + else: + new_hours = room.inactive_hours + amount + room = await self.rooms_dao.update( + room_id, inactive_hours=new_hours + ) + + return transaction, room + + async def remove_hours( + self, + room_id: int, + amount: int, + admin_id: int | None = None, + is_active: bool = True, + ) -> tuple[RoomHoursTransaction, Room | None]: + transaction = RoomHoursTransaction( + room_id=room_id, + transaction_type=TransactionType.DECREASE.value, + amount=amount, + admin_id=admin_id, + ) + transaction = await self.transactions_dao.create(transaction) + + room = await self.rooms_dao.get_by_id(room_id) + if room: + if is_active: + new_hours = max(0, room.active_hours - amount) + room = await self.rooms_dao.update( + room_id, active_hours=new_hours + ) + else: + new_hours = max(0, room.inactive_hours - amount) + room = await self.rooms_dao.update( + room_id, inactive_hours=new_hours + ) + + return transaction, room + + async def move_hours_to_completed( + self, + room_id: int, + amount: int, + admin_id: int | None = None, + ) -> tuple[RoomHoursTransaction, Room | None]: + """Перемещает часы из неотработанных в отработанные""" + transaction = RoomHoursTransaction( + room_id=room_id, + transaction_type=TransactionType.DECREASE.value, + amount=amount, + admin_id=admin_id, + ) + transaction = await self.transactions_dao.create(transaction) + + room = await self.rooms_dao.get_by_id(room_id) + if room: + new_active = max(0, room.active_hours - amount) + new_inactive = room.inactive_hours + amount + room = await self.rooms_dao.update( + room_id, + active_hours=new_active, + inactive_hours=new_inactive + ) + + return transaction, room + + async def get_room_history(self, room_id: int) -> list[RoomHoursTransaction]: + return await self.transactions_dao.get_by_room_id(room_id) + + async def get_all_transactions(self) -> list[RoomHoursTransaction]: + return await self.transactions_dao.get_all() + + async def get_transaction_by_id( + self, transaction_id: int + ) -> RoomHoursTransaction | None: + return await self.transactions_dao.get_by_id(transaction_id) diff --git a/src/dutylog/infrastructure/ioc.py b/src/dutylog/infrastructure/ioc.py index 902d888..e88ec3f 100644 --- a/src/dutylog/infrastructure/ioc.py +++ b/src/dutylog/infrastructure/ioc.py @@ -8,6 +8,9 @@ from dutylog.infrastructure.database.dao.users_dao import UsersDAO from dutylog.infrastructure.database.dao.hours_transactions_dao import ( HoursTransactionsDAO, ) +from dutylog.infrastructure.database.dao.room_hours_transactions_dao import ( + RoomHoursTransactionsDAO, +) 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 @@ -20,6 +23,9 @@ from dutylog.infrastructure.database.repositories.users_repository import ( from dutylog.infrastructure.database.repositories.hours_transactions_repository import ( HoursTransactionsRepository, ) +from dutylog.infrastructure.database.repositories.room_hours_transactions_repository import ( + RoomHoursTransactionsRepository, +) from dutylog.infrastructure.database.repositories.rooms_repository import ( RoomsRepository, ) @@ -70,6 +76,10 @@ class DAOProvider(Provider): def get_hours_transactions_dao(self, session: AsyncSession) -> HoursTransactionsDAO: return HoursTransactionsDAO(session) + @provide(scope=Scope.REQUEST) + def get_room_hours_transactions_dao(self, session: AsyncSession) -> RoomHoursTransactionsDAO: + return RoomHoursTransactionsDAO(session) + @provide(scope=Scope.REQUEST) def get_rooms_dao(self, session: AsyncSession) -> RoomsDAO: return RoomsDAO(session) @@ -100,6 +110,14 @@ class RepositoryProvider(Provider): ) -> HoursTransactionsRepository: return HoursTransactionsRepository(transactions_dao, residents_dao) + @provide(scope=Scope.REQUEST) + def get_room_hours_transactions_repository( + self, + transactions_dao: RoomHoursTransactionsDAO, + rooms_dao: RoomsDAO, + ) -> RoomHoursTransactionsRepository: + return RoomHoursTransactionsRepository(transactions_dao, rooms_dao) + @provide(scope=Scope.REQUEST) def get_rooms_repository(self, rooms_dao: RoomsDAO) -> RoomsRepository: return RoomsRepository(rooms_dao)