This commit is contained in:
2026-02-27 22:44:02 +03:00
parent 55c3629868
commit c57a40b09a
14 changed files with 331 additions and 69 deletions
@@ -20,10 +20,10 @@ class HoursTransactionsDAO:
)
return list(result.scalars().all())
async def get_by_user_id(self, user_id: int) -> list[HoursTransaction]:
async def get_by_resident_id(self, resident_id: int) -> list[HoursTransaction]:
result = await self.session.execute(
select(HoursTransaction)
.where(HoursTransaction.user_id == user_id)
.where(HoursTransaction.resident_id == resident_id)
.order_by(HoursTransaction.created_at.desc())
)
return list(result.scalars().all())
@@ -0,0 +1,56 @@
from sqlalchemy import select, update, delete
from sqlalchemy.ext.asyncio import AsyncSession
from dutylog.infrastructure.database.models.resident import Resident
class ResidentsDAO:
def __init__(self, session: AsyncSession):
self.session = session
async def get_by_id(self, resident_id: int) -> Resident | None:
result = await self.session.execute(
select(Resident).where(Resident.id == resident_id)
)
return result.scalar_one_or_none()
async def get_by_user_id(self, user_id: int) -> Resident | None:
result = await self.session.execute(
select(Resident).where(Resident.user_entity == user_id)
)
return result.scalar_one_or_none()
async def get_by_room(self, room_id: int) -> list[Resident]:
result = await self.session.execute(
select(Resident).where(Resident.room == room_id)
)
return list(result.scalars().all())
async def get_available(self) -> list[Resident]:
result = await self.session.execute(
select(Resident).where(Resident.is_busy == False)
)
return list(result.scalars().all())
async def get_all(self) -> list[Resident]:
result = await self.session.execute(select(Resident))
return list(result.scalars().all())
async def create(self, resident: Resident) -> Resident:
self.session.add(resident)
await self.session.commit()
await self.session.refresh(resident)
return resident
async def update(self, resident_id: int, **kwargs) -> Resident | None:
await self.session.execute(
update(Resident).where(Resident.id == resident_id).values(**kwargs)
)
await self.session.commit()
return await self.get_by_id(resident_id)
async def delete(self, resident_id: int) -> None:
await self.session.execute(
delete(Resident).where(Resident.id == resident_id)
)
await self.session.commit()
@@ -0,0 +1,44 @@
from sqlalchemy import select, update, delete
from sqlalchemy.ext.asyncio import AsyncSession
from dutylog.infrastructure.database.models.room import Room
class RoomsDAO:
def __init__(self, session: AsyncSession):
self.session = session
async def get_by_id(self, room_id: int) -> Room | None:
result = await self.session.execute(
select(Room).where(Room.id == room_id)
)
return result.scalar_one_or_none()
async def get_by_number(self, number: int) -> Room | None:
result = await self.session.execute(
select(Room).where(Room.number == number)
)
return result.scalar_one_or_none()
async def get_all(self) -> list[Room]:
result = await self.session.execute(select(Room))
return list(result.scalars().all())
async def create(self, room: Room) -> Room:
self.session.add(room)
await self.session.commit()
await self.session.refresh(room)
return room
async def update(self, room_id: int, **kwargs) -> Room | None:
await self.session.execute(
update(Room).where(Room.id == room_id).values(**kwargs)
)
await self.session.commit()
return await self.get_by_id(room_id)
async def delete(self, room_id: int) -> None:
await self.session.execute(
delete(Room).where(Room.id == room_id)
)
await self.session.commit()
@@ -1,5 +1,7 @@
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 import Room
from dutylog.infrastructure.database.models.resident import Resident
__all__ = ["Base", "User", "HoursTransaction"]
__all__ = ["Base", "User", "HoursTransaction", "Room", "Resident"]
@@ -1,4 +1,4 @@
from datetime import datetime, timezone, timedelta
from datetime import datetime
from enum import Enum
from sqlalchemy import BigInteger, Integer, String, DateTime, ForeignKey
@@ -17,7 +17,7 @@ class HoursTransaction(Base):
__tablename__ = "hours_transactions"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
user_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
resident_id: Mapped[int] = mapped_column(Integer, ForeignKey("residents.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)
@@ -0,0 +1,21 @@
from datetime import datetime
from sqlalchemy import BigInteger, Boolean, 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 Resident(Base):
__tablename__ = "residents"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
real_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
room: Mapped[int] = mapped_column(Integer, ForeignKey("rooms.id", ondelete="CASCADE"), nullable=False)
user_entity: Mapped[int | None] = mapped_column(BigInteger, ForeignKey("users.id", ondelete="SET NULL"), nullable=True, unique=True)
is_busy: Mapped[bool] = mapped_column(Boolean, default=False, server_default="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")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=msk_now)
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=msk_now, onupdate=msk_now)
@@ -0,0 +1,11 @@
from sqlalchemy import Integer
from sqlalchemy.orm import Mapped, mapped_column
from dutylog.infrastructure.database.models.base import Base
class Room(Base):
__tablename__ = "rooms"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
number: Mapped[int] = mapped_column(Integer, nullable=False, unique=True)
@@ -1,6 +1,6 @@
from datetime import datetime
from sqlalchemy import BigInteger, Boolean, Integer, String, DateTime
from sqlalchemy import BigInteger, Boolean, String, DateTime
from sqlalchemy.orm import Mapped, mapped_column
from dutylog.infrastructure.database.models.base import Base
@@ -15,7 +15,5 @@ class User(Base):
first_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
last_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
is_admin: Mapped[bool] = mapped_column(Boolean, default=False, server_default="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")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=msk_now)
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=msk_now, onupdate=msk_now)
@@ -1,72 +1,72 @@
from dutylog.infrastructure.database.dao.hours_transactions_dao import HoursTransactionsDAO
from dutylog.infrastructure.database.dao.users_dao import UsersDAO
from dutylog.infrastructure.database.dao.residents_dao import ResidentsDAO
from dutylog.infrastructure.database.models.hours_transaction import HoursTransaction, TransactionType
from dutylog.infrastructure.database.models.user import User
from dutylog.infrastructure.database.models.resident import Resident
class HoursTransactionsRepository:
def __init__(
self,
transactions_dao: HoursTransactionsDAO,
users_dao: UsersDAO,
residents_dao: ResidentsDAO,
):
self.transactions_dao = transactions_dao
self.users_dao = users_dao
self.residents_dao = residents_dao
async def add_hours(
self,
user_id: int,
resident_id: int,
amount: int,
admin_id: int | None = None,
is_active: bool = True,
) -> tuple[HoursTransaction, User | None]:
) -> tuple[HoursTransaction, Resident | None]:
transaction = HoursTransaction(
user_id=user_id,
resident_id=resident_id,
transaction_type=TransactionType.INCREASE.value,
amount=amount,
admin_id=admin_id,
)
transaction = await self.transactions_dao.create(transaction)
user = await self.users_dao.get_by_id(user_id)
if user:
resident = await self.residents_dao.get_by_id(resident_id)
if resident:
if is_active:
new_hours = user.active_hours + amount
user = await self.users_dao.update(user_id, active_hours=new_hours)
new_hours = resident.active_hours + amount
resident = await self.residents_dao.update(resident_id, active_hours=new_hours)
else:
new_hours = user.inactive_hours + amount
user = await self.users_dao.update(user_id, inactive_hours=new_hours)
new_hours = resident.inactive_hours + amount
resident = await self.residents_dao.update(resident_id, inactive_hours=new_hours)
return transaction, user
return transaction, resident
async def remove_hours(
self,
user_id: int,
resident_id: int,
amount: int,
admin_id: int | None = None,
is_active: bool = True,
) -> tuple[HoursTransaction, User | None]:
) -> tuple[HoursTransaction, Resident | None]:
transaction = HoursTransaction(
user_id=user_id,
resident_id=resident_id,
transaction_type=TransactionType.DECREASE.value,
amount=amount,
admin_id=admin_id,
)
transaction = await self.transactions_dao.create(transaction)
user = await self.users_dao.get_by_id(user_id)
if user:
resident = await self.residents_dao.get_by_id(resident_id)
if resident:
if is_active:
new_hours = max(0, user.active_hours - amount)
user = await self.users_dao.update(user_id, active_hours=new_hours)
new_hours = max(0, resident.active_hours - amount)
resident = await self.residents_dao.update(resident_id, active_hours=new_hours)
else:
new_hours = max(0, user.inactive_hours - amount)
user = await self.users_dao.update(user_id, inactive_hours=new_hours)
new_hours = max(0, resident.inactive_hours - amount)
resident = await self.residents_dao.update(resident_id, inactive_hours=new_hours)
return transaction, user
return transaction, resident
async def get_user_history(self, user_id: int) -> list[HoursTransaction]:
return await self.transactions_dao.get_by_user_id(user_id)
async def get_resident_history(self, resident_id: int) -> list[HoursTransaction]:
return await self.transactions_dao.get_by_resident_id(resident_id)
async def get_all_transactions(self) -> list[HoursTransaction]:
return await self.transactions_dao.get_all()
@@ -0,0 +1,74 @@
from dutylog.infrastructure.database.dao.residents_dao import ResidentsDAO
from dutylog.infrastructure.database.models.resident import Resident
class ResidentsRepository:
def __init__(self, residents_dao: ResidentsDAO):
self.residents_dao = residents_dao
async def create_resident(
self,
room_id: int,
real_name: str | None = None,
) -> Resident:
resident = Resident(
room=room_id,
real_name=real_name,
)
return await self.residents_dao.create(resident)
async def bind_user_to_resident(self, resident_id: int, user_id: int) -> Resident | None:
return await self.residents_dao.update(
resident_id,
user_entity=user_id,
is_busy=True,
)
async def unbind_user_from_resident(self, resident_id: int) -> Resident | None:
return await self.residents_dao.update(
resident_id,
user_entity=None,
is_busy=False,
)
async def update_resident_info(
self,
resident_id: int,
real_name: str | None = None,
) -> Resident | None:
return await self.residents_dao.update(
resident_id,
real_name=real_name,
)
async def add_active_hours(self, resident_id: int, hours: int) -> Resident | None:
resident = await self.residents_dao.get_by_id(resident_id)
if resident:
new_hours = resident.active_hours + hours
return await self.residents_dao.update(resident_id, active_hours=new_hours)
return None
async def add_inactive_hours(self, resident_id: int, hours: int) -> Resident | None:
resident = await self.residents_dao.get_by_id(resident_id)
if resident:
new_hours = resident.inactive_hours + hours
return await self.residents_dao.update(resident_id, inactive_hours=new_hours)
return None
async def get_resident_by_id(self, resident_id: int) -> Resident | None:
return await self.residents_dao.get_by_id(resident_id)
async def get_resident_by_user_id(self, user_id: int) -> Resident | None:
return await self.residents_dao.get_by_user_id(user_id)
async def get_residents_by_room(self, room_id: int) -> list[Resident]:
return await self.residents_dao.get_by_room(room_id)
async def get_available_residents(self) -> list[Resident]:
return await self.residents_dao.get_available()
async def get_all_residents(self) -> list[Resident]:
return await self.residents_dao.get_all()
async def delete_resident(self, resident_id: int) -> None:
await self.residents_dao.delete(resident_id)
@@ -0,0 +1,26 @@
from dutylog.infrastructure.database.dao.rooms_dao import RoomsDAO
from dutylog.infrastructure.database.models.room import Room
class RoomsRepository:
def __init__(self, rooms_dao: RoomsDAO):
self.rooms_dao = rooms_dao
async def get_or_create_room(self, number: int) -> Room:
room = await self.rooms_dao.get_by_number(number)
if not room:
room = Room(number=number)
room = await self.rooms_dao.create(room)
return room
async def get_room_by_id(self, room_id: int) -> Room | None:
return await self.rooms_dao.get_by_id(room_id)
async def get_room_by_number(self, number: int) -> Room | None:
return await self.rooms_dao.get_by_number(number)
async def get_all_rooms(self) -> list[Room]:
return await self.rooms_dao.get_all()
async def delete_room(self, room_id: int) -> None:
await self.rooms_dao.delete(room_id)
@@ -41,20 +41,6 @@ class UsersRepository:
async def set_admin_status(self, user_id: int, is_admin: bool) -> User | None:
return await self.users_dao.update(user_id, is_admin=is_admin)
async def add_active_hours(self, user_id: int, hours: int) -> User | None:
user = await self.users_dao.get_by_id(user_id)
if user:
new_hours = user.active_hours + hours
return await self.users_dao.update(user_id, active_hours=new_hours)
return None
async def add_inactive_hours(self, user_id: int, hours: int) -> User | None:
user = await self.users_dao.get_by_id(user_id)
if user:
new_hours = user.inactive_hours + hours
return await self.users_dao.update(user_id, inactive_hours=new_hours)
return None
async def get_all_admins(self) -> list[User]:
all_users = await self.users_dao.get_all()
return [user for user in all_users if user.is_admin]
+23 -2
View File
@@ -6,8 +6,12 @@ from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker
from dutylog.infrastructure.database.config import create_engine, create_session_maker
from dutylog.infrastructure.database.dao.users_dao import UsersDAO
from dutylog.infrastructure.database.dao.hours_transactions_dao import HoursTransactionsDAO
from dutylog.infrastructure.database.dao.rooms_dao import RoomsDAO
from dutylog.infrastructure.database.dao.residents_dao import ResidentsDAO
from dutylog.infrastructure.database.repositories.users_repository import UsersRepository
from dutylog.infrastructure.database.repositories.hours_transactions_repository import HoursTransactionsRepository
from dutylog.infrastructure.database.repositories.rooms_repository import RoomsRepository
from dutylog.infrastructure.database.repositories.residents_repository import ResidentsRepository
from dutylog.infrastructure.utils.config import Config, load_config
@@ -43,6 +47,14 @@ class DAOProvider(Provider):
def get_hours_transactions_dao(self, session: AsyncSession) -> HoursTransactionsDAO:
return HoursTransactionsDAO(session)
@provide(scope=Scope.REQUEST)
def get_rooms_dao(self, session: AsyncSession) -> RoomsDAO:
return RoomsDAO(session)
@provide(scope=Scope.REQUEST)
def get_residents_dao(self, session: AsyncSession) -> ResidentsDAO:
return ResidentsDAO(session)
class RepositoryProvider(Provider):
@provide(scope=Scope.REQUEST)
@@ -53,9 +65,18 @@ class RepositoryProvider(Provider):
def get_hours_transactions_repository(
self,
transactions_dao: HoursTransactionsDAO,
users_dao: UsersDAO,
residents_dao: ResidentsDAO,
) -> HoursTransactionsRepository:
return HoursTransactionsRepository(transactions_dao, users_dao)
return HoursTransactionsRepository(transactions_dao, residents_dao)
@provide(scope=Scope.REQUEST)
def get_rooms_repository(self, rooms_dao: RoomsDAO) -> RoomsRepository:
return RoomsRepository(rooms_dao)
@provide(scope=Scope.REQUEST)
def get_residents_repository(self, residents_dao: ResidentsDAO) -> ResidentsRepository:
return ResidentsRepository(residents_dao)