This commit is contained in:
2026-02-28 11:20:41 +03:00
parent b9ef6ccf09
commit 3c0d50a1aa
19 changed files with 544 additions and 185 deletions
+7 -4
View File
@@ -12,7 +12,12 @@ 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.admin_dialogs import admin_menu_dialog
from dutylog.application.bot.user_dialogs.registration_dialog import registration_dialog
from dutylog.infrastructure.ioc import ConfigProvider, DatabaseProvider, DAOProvider, RepositoryProvider
from dutylog.infrastructure.ioc import (
ConfigProvider,
DatabaseProvider,
DAOProvider,
RepositoryProvider,
)
from dutylog.infrastructure.utils.config import load_config
@@ -22,8 +27,7 @@ async def main():
config = load_config()
bot = Bot(
token=config.bot.token,
default=DefaultBotProperties(parse_mode=ParseMode.HTML)
token=config.bot.token, default=DefaultBotProperties(parse_mode=ParseMode.HTML)
)
await bot.delete_webhook(drop_pending_updates=True)
@@ -50,4 +54,3 @@ async def main():
if __name__ == "__main__":
asyncio.run(main())
@@ -1,3 +1,5 @@
from dutylog.application.bot.user_dialogs.admin_dialogs.admin_menu_dialog import admin_menu_dialog
from dutylog.application.bot.user_dialogs.admin_dialogs.admin_menu_dialog import (
admin_menu_dialog,
)
__all__ = ["admin_menu_dialog"]
@@ -3,15 +3,24 @@ from aiogram import Bot
from aiogram.exceptions import TelegramForbiddenError, TelegramBadRequest
from aiogram_dialog import Dialog, Window, DialogManager
from aiogram_dialog.widgets.text import Format, Const
from aiogram_dialog.widgets.kbd import Row, SwitchTo, Button, ScrollingGroup, Select
from aiogram_dialog.widgets.kbd import Row, SwitchTo, Button, ScrollingGroup, Select, Group
from aiogram_dialog.widgets.input import MessageInput
from dishka import FromDishka
from dishka.integrations.aiogram_dialog import inject
from dutylog.application.bot.user_dialogs.states import AdminMenuSG
from dutylog.infrastructure.database.repositories.users_repository import UsersRepository
from dutylog.infrastructure.database.repositories.residents_repository import ResidentsRepository
from dutylog.infrastructure.database.repositories.rooms_repository import RoomsRepository
from dutylog.infrastructure.database.repositories.users_repository import (
UsersRepository,
)
from dutylog.infrastructure.database.repositories.residents_repository import (
ResidentsRepository,
)
from dutylog.infrastructure.database.repositories.rooms_repository import (
RoomsRepository,
)
from dutylog.infrastructure.database.repositories.hours_transactions_repository import (
HoursTransactionsRepository,
)
from dutylog.infrastructure.utils.config import Config
@@ -129,8 +138,6 @@ async def on_broadcast_confirm(
failed_count += 1
except TelegramBadRequest:
failed_count += 1
except Exception:
failed_count += 1
result_text = f"""
<blockquote>📢 <b>Результаты рассылки</b></blockquote>
@@ -174,10 +181,9 @@ async def get_residents_list_data(
status = "🟢" if resident.is_busy else "⚪️"
name = resident.real_name if resident.real_name else "Без имени"
residents_data.append((
f"{name} | Комната {room_number} | {status}",
resident.id
))
residents_data.append(
(f"{name} | Комната {room_number} | {status}", resident.id)
)
content = f"""
<blockquote>🏠 <b>Резиденты</b></blockquote>
@@ -221,7 +227,10 @@ async def get_resident_info_data(
if resident.user_entity:
user = await users_repository.get_user_by_id(resident.user_entity)
if user:
username = f"@{user.username}" if user.username else "без username"
if user.username:
username = f"@{user.username}"
else:
username = f"ID: {user.id}"
user_info = f"{user.first_name} ({username})"
info_content = f"""
@@ -303,6 +312,142 @@ async def on_logout_resident_cancel(
await dialog_manager.switch_to(AdminMenuSG.resident_info)
async def on_add_hours_click(
callback: CallbackQuery,
button: Button,
dialog_manager: DialogManager,
):
await dialog_manager.switch_to(AdminMenuSG.add_hours_select)
async def on_remove_hours_click(
callback: CallbackQuery,
button: Button,
dialog_manager: DialogManager,
):
await dialog_manager.switch_to(AdminMenuSG.remove_hours_select)
async def get_hours_select_data(**kwargs):
hours_options = [
(5, "5"), (10, "10"), (15, "15"), (20, "20"),
(25, "25"), (30, "30"), (35, "35"), (40, "40"),
(45, "45"), (50, "50"), (55, "55"), (60, "60"),
(65, "65"), (70, "70"), (75, "75"), (80, "80"),
]
return {"hours_options": hours_options}
async def on_hours_selected(
callback: CallbackQuery,
widget: Select,
dialog_manager: DialogManager,
item_id: str,
):
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)
else:
await dialog_manager.switch_to(AdminMenuSG.remove_hours_confirm)
async def on_custom_hours_click(
callback: CallbackQuery,
button: Button,
dialog_manager: DialogManager,
):
if dialog_manager.current_context().state == AdminMenuSG.add_hours_select:
await dialog_manager.switch_to(AdminMenuSG.add_hours_custom)
else:
await dialog_manager.switch_to(AdminMenuSG.remove_hours_custom)
async def on_custom_hours_input(
message: Message,
widget: MessageInput,
dialog_manager: DialogManager,
):
if not message.text:
await message.answer("⚠️ Пожалуйста, введите число")
return
try:
hours = int(message.text)
if hours <= 0:
await message.answer("⚠️ Количество часов должно быть положительным числом")
return
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)
else:
await dialog_manager.switch_to(AdminMenuSG.remove_hours_confirm)
except ValueError:
await message.answer("⚠️ Пожалуйста, введите корректное число")
async def get_hours_confirm_data(
dialog_manager: DialogManager,
**kwargs,
):
hours = dialog_manager.dialog_data.get("selected_hours", 0)
return {"hours": hours}
@inject
async def on_add_hours_confirm(
callback: CallbackQuery,
button: Button,
dialog_manager: DialogManager,
transactions_repository: FromDishka[HoursTransactionsRepository],
):
resident_id = dialog_manager.dialog_data.get("selected_resident_id")
hours = dialog_manager.dialog_data.get("selected_hours")
admin_id = callback.from_user.id
if resident_id and hours:
await transactions_repository.add_hours(
resident_id=resident_id,
amount=hours,
admin_id=admin_id,
is_active=True,
)
await dialog_manager.switch_to(AdminMenuSG.resident_info)
@inject
async def on_remove_hours_confirm(
callback: CallbackQuery,
button: Button,
dialog_manager: DialogManager,
transactions_repository: FromDishka[HoursTransactionsRepository],
):
resident_id = dialog_manager.dialog_data.get("selected_resident_id")
hours = dialog_manager.dialog_data.get("selected_hours")
admin_id = callback.from_user.id
if resident_id and hours:
await transactions_repository.remove_hours(
resident_id=resident_id,
amount=hours,
admin_id=admin_id,
is_active=True,
)
await dialog_manager.switch_to(AdminMenuSG.resident_info)
async def on_hours_cancel(
callback: CallbackQuery,
button: Button,
dialog_manager: DialogManager,
):
await dialog_manager.switch_to(AdminMenuSG.resident_info)
admin_menu_dialog = Dialog(
Window(
Format("{content}"),
@@ -311,11 +456,6 @@ admin_menu_dialog = Dialog(
id="residents_btn",
state=AdminMenuSG.residents,
),
SwitchTo(
Const("👥 Пользователи"),
id="users_btn",
state=AdminMenuSG.users,
),
SwitchTo(
Const("📊 Статистика"),
id="stats_btn",
@@ -370,6 +510,18 @@ admin_menu_dialog = Dialog(
),
Window(
Format("{info_content}"),
Row(
Button(
Const(" Добавить часы"),
id="add_hours_btn",
on_click=on_add_hours_click,
),
Button(
Const(" Отнять часы"),
id="remove_hours_btn",
on_click=on_remove_hours_click,
),
),
Button(
Const("🚪 Разлогинить"),
id="logout_resident_btn",
@@ -385,7 +537,9 @@ admin_menu_dialog = Dialog(
getter=get_resident_info_data,
),
Window(
Const("<blockquote>⚠️ <b>Подтверждение</b></blockquote>\n\nВы уверены, что хотите разлогинить этого резидента?"),
Const(
"<blockquote>⚠️ <b>Подтверждение</b></blockquote>\n\nВы уверены, что хотите разлогинить этого резидента?"
),
Row(
Button(
Const("✅ Да"),
@@ -401,9 +555,108 @@ admin_menu_dialog = Dialog(
state=AdminMenuSG.resident_logout_confirm,
),
Window(
Const("<blockquote>👥 <b>Пользователи</b></blockquote>\n\n<i>Функционал в разработке</i>"),
SwitchTo(Const("◀️ Назад"), id="back_from_users", state=AdminMenuSG.main),
state=AdminMenuSG.users,
Const("<blockquote> <b>Добавить часы</b></blockquote>\n\nВыберите количество часов:"),
Group(
Select(
Format("{item[1]} ч"),
id="hours_select_add",
item_id_getter=lambda x: x[0],
items="hours_options",
on_click=on_hours_selected,
),
width=4,
),
Button(
Const("✏️ Ввести свое количество"),
id="custom_hours_add_btn",
on_click=on_custom_hours_click,
),
SwitchTo(
Const("◀️ Отмена"),
id="cancel_add_hours",
state=AdminMenuSG.resident_info,
),
state=AdminMenuSG.add_hours_select,
getter=get_hours_select_data,
),
Window(
Const("<blockquote> <b>Отнять часы</b></blockquote>\n\nВыберите количество часов:"),
Group(
Select(
Format("{item[1]} ч"),
id="hours_select_remove",
item_id_getter=lambda x: x[0],
items="hours_options",
on_click=on_hours_selected,
),
width=4,
),
Button(
Const("✏️ Ввести свое количество"),
id="custom_hours_remove_btn",
on_click=on_custom_hours_click,
),
SwitchTo(
Const("◀️ Отмена"),
id="cancel_remove_hours",
state=AdminMenuSG.resident_info,
),
state=AdminMenuSG.remove_hours_select,
getter=get_hours_select_data,
),
Window(
Const("<blockquote>✏️ <b>Добавить часы</b></blockquote>\n\nВведите количество часов:"),
MessageInput(on_custom_hours_input),
SwitchTo(
Const("◀️ Отмена"),
id="cancel_custom_add",
state=AdminMenuSG.add_hours_select,
),
state=AdminMenuSG.add_hours_custom,
),
Window(
Const("<blockquote>✏️ <b>Отнять часы</b></blockquote>\n\nВведите количество часов:"),
MessageInput(on_custom_hours_input),
SwitchTo(
Const("◀️ Отмена"),
id="cancel_custom_remove",
state=AdminMenuSG.remove_hours_select,
),
state=AdminMenuSG.remove_hours_custom,
),
Window(
Format("<blockquote> <b>Подтверждение</b></blockquote>\n\nВы уверены, что хотите добавить <code>{hours}</code> часов?"),
Row(
Button(
Const("✅ Да"),
id="confirm_add_hours",
on_click=on_add_hours_confirm,
),
Button(
Const("❌ Нет"),
id="cancel_add_hours_confirm",
on_click=on_hours_cancel,
),
),
state=AdminMenuSG.add_hours_confirm,
getter=get_hours_confirm_data,
),
Window(
Format("<blockquote> <b>Подтверждение</b></blockquote>\n\nВы уверены, что хотите отнять <code>{hours}</code> часов?"),
Row(
Button(
Const("✅ Да"),
id="confirm_remove_hours",
on_click=on_remove_hours_confirm,
),
Button(
Const("❌ Нет"),
id="cancel_remove_hours_confirm",
on_click=on_hours_cancel,
),
),
state=AdminMenuSG.remove_hours_confirm,
getter=get_hours_confirm_data,
),
Window(
Format("{stats_content}"),
@@ -412,16 +665,26 @@ admin_menu_dialog = Dialog(
getter=get_statistics_data,
),
Window(
Const("<blockquote>📢 <b>Рассылка</b></blockquote>\n\nОтправьте сообщение, которое хотите разослать всем пользователям:"),
Const(
"<blockquote>📢 <b>Рассылка</b></blockquote>\n\nОтправьте сообщение, которое хотите разослать всем пользователям:"
),
MessageInput(on_broadcast_message),
SwitchTo(Const("◀️ Отмена"), id="cancel_broadcast", state=AdminMenuSG.main),
state=AdminMenuSG.broadcast,
),
Window(
Const("<blockquote>📢 <b>Подтверждение рассылки</b></blockquote>\n\n⚠️ Вы уверены, что хотите отправить это сообщение всем пользователям?"),
Const(
"<blockquote>📢 <b>Подтверждение рассылки</b></blockquote>\n\n⚠️ Вы уверены, что хотите отправить это сообщение всем пользователям?"
),
Row(
Button(Const("✅ Да"), id="confirm_broadcast", on_click=on_broadcast_confirm),
Button(Const("❌ Нет"), id="cancel_broadcast_confirm", on_click=on_broadcast_cancel),
Button(
Const("✅ Да"), id="confirm_broadcast", on_click=on_broadcast_confirm
),
Button(
Const("❌ Нет"),
id="cancel_broadcast_confirm",
on_click=on_broadcast_cancel,
),
),
state=AdminMenuSG.broadcast_confirm,
),
@@ -6,10 +6,18 @@ from dishka import FromDishka
from dishka.integrations.aiogram_dialog import inject
from dutylog.application.bot.user_dialogs.states import MainMenuSG
from dutylog.infrastructure.database.repositories.users_repository import UsersRepository
from dutylog.infrastructure.database.repositories.residents_repository import ResidentsRepository
from dutylog.infrastructure.database.repositories.rooms_repository import RoomsRepository
from dutylog.infrastructure.database.repositories.hours_transactions_repository import HoursTransactionsRepository
from dutylog.infrastructure.database.repositories.users_repository import (
UsersRepository,
)
from dutylog.infrastructure.database.repositories.residents_repository import (
ResidentsRepository,
)
from dutylog.infrastructure.database.repositories.rooms_repository import (
RoomsRepository,
)
from dutylog.infrastructure.database.repositories.hours_transactions_repository import (
HoursTransactionsRepository,
)
from dutylog.infrastructure.utils.config import Config
@@ -37,17 +45,25 @@ async def get_main_menu_data(
elif is_admin:
greeting = "👨‍💼 <b>Администратор</b>"
else:
resident = await residents_repository.get_resident_by_user_id(event_from_user.id)
resident = await residents_repository.get_resident_by_user_id(
event_from_user.id
)
if resident:
room = await rooms_repository.get_room_by_id(resident.room)
room_number = room.number if room else "???"
real_name = resident.real_name if resident.real_name else event_from_user.first_name
greeting = f"👋 <b>Привет, {real_name}!</b>\n🚪 Комната <code>{room_number}</code>"
real_name = (
resident.real_name if resident.real_name else event_from_user.first_name
)
greeting = (
f"👋 <b>Привет, {real_name}!</b>\n🚪 Комната <code>{room_number}</code>"
)
else:
greeting = f"👋 <b>Привет, {event_from_user.first_name}!</b>"
if not is_admin and not is_creator:
resident = await residents_repository.get_resident_by_user_id(event_from_user.id)
resident = await residents_repository.get_resident_by_user_id(
event_from_user.id
)
if not resident:
content = f"""
@@ -177,4 +193,3 @@ main_menu_dialog = Dialog(
state=MainMenuSG.faq,
),
)
@@ -7,9 +7,15 @@ from dishka import FromDishka
from dishka.integrations.aiogram_dialog import inject
from dutylog.application.bot.user_dialogs.states import RegistrationSG, MainMenuSG
from dutylog.infrastructure.database.repositories.floors_repository import FloorsRepository
from dutylog.infrastructure.database.repositories.rooms_repository import RoomsRepository
from dutylog.infrastructure.database.repositories.residents_repository import ResidentsRepository
from dutylog.infrastructure.database.repositories.floors_repository import (
FloorsRepository,
)
from dutylog.infrastructure.database.repositories.rooms_repository import (
RoomsRepository,
)
from dutylog.infrastructure.database.repositories.residents_repository import (
ResidentsRepository,
)
@inject
@@ -75,7 +81,9 @@ async def get_residents_data(
available_residents = [r for r in residents if not r.is_busy]
return {
"residents": [(r.id, r.real_name or f"Резидент #{r.id}") for r in available_residents],
"residents": [
(r.id, r.real_name or f"Резидент #{r.id}") for r in available_residents
],
}
@@ -118,8 +126,14 @@ async def on_resident_selected(
registration_dialog = Dialog(
Window(
Const("<blockquote>🏢 <b>Выбор этажа</b></blockquote>\n\n<blockquote>⚠️ <b>Внимание!</b> Перерегистрацию может выполнить только администратор. Выбирайте внимательно!</blockquote>\n\nВыберите этаж, на котором вы живете:", when="has_available"),
Const("<blockquote>⚠️ <b>Нет доступных резидентов</b></blockquote>\n\nВсе резиденты уже заняты.\nОбратитесь к администратору.", when=~F["has_available"]),
Const(
"<blockquote>🏢 <b>Выбор этажа</b></blockquote>\n\n<blockquote>⚠️ <b>Внимание!</b> Перерегистрацию может выполнить только администратор. Выбирайте внимательно!</blockquote>\n\nВыберите этаж, на котором вы живете:",
when="has_available",
),
Const(
"<blockquote>⚠️ <b>Нет доступных резидентов</b></blockquote>\n\nВсе резиденты уже заняты.\nОбратитесь к администратору.",
when=~F["has_available"],
),
Group(
Select(
Format("{item[1]}"),
@@ -135,7 +149,9 @@ registration_dialog = Dialog(
getter=get_floors_data,
),
Window(
Const("<blockquote>🚪 <b>Выбор комнаты</b></blockquote>\n\nВыберите вашу комнату:"),
Const(
"<blockquote>🚪 <b>Выбор комнаты</b></blockquote>\n\nВыберите вашу комнату:"
),
Group(
Select(
Format("{item[1]}"),
@@ -146,12 +162,16 @@ registration_dialog = Dialog(
),
width=3,
),
SwitchTo(Const("◀️ Назад"), id="back_to_floors", state=RegistrationSG.select_floor),
SwitchTo(
Const("◀️ Назад"), id="back_to_floors", state=RegistrationSG.select_floor
),
state=RegistrationSG.select_room,
getter=get_rooms_data,
),
Window(
Const("<blockquote>👤 <b>Выбор резидента</b></blockquote>\n\nВыберите себя из списка:"),
Const(
"<blockquote>👤 <b>Выбор резидента</b></blockquote>\n\nВыберите себя из списка:"
),
Group(
Select(
Format("{item[1]}"),
@@ -162,7 +182,9 @@ registration_dialog = Dialog(
),
width=1,
),
SwitchTo(Const("◀️ Назад"), id="back_to_rooms", state=RegistrationSG.select_room),
SwitchTo(
Const("◀️ Назад"), id="back_to_rooms", state=RegistrationSG.select_room
),
state=RegistrationSG.select_resident,
getter=get_residents_data,
),
@@ -12,7 +12,12 @@ class AdminMenuSG(StatesGroup):
residents = State()
resident_info = State()
resident_logout_confirm = State()
users = State()
add_hours_select = State()
remove_hours_select = State()
add_hours_custom = State()
remove_hours_custom = State()
add_hours_confirm = State()
remove_hours_confirm = State()
statistics = State()
broadcast = State()
broadcast_confirm = State()
+14 -4
View File
@@ -4,9 +4,17 @@ from aiogram.types import Message
from aiogram_dialog import DialogManager, StartMode
from dishka import FromDishka
from dutylog.application.bot.user_dialogs.states import MainMenuSG, AdminMenuSG, RegistrationSG
from dutylog.infrastructure.database.repositories.users_repository import UsersRepository
from dutylog.infrastructure.database.repositories.residents_repository import ResidentsRepository
from dutylog.application.bot.user_dialogs.states import (
MainMenuSG,
AdminMenuSG,
RegistrationSG,
)
from dutylog.infrastructure.database.repositories.users_repository import (
UsersRepository,
)
from dutylog.infrastructure.database.repositories.residents_repository import (
ResidentsRepository,
)
from dutylog.infrastructure.utils.config import Config
router = Router()
@@ -31,7 +39,9 @@ async def start_handler(
return
if user:
resident = await residents_repository.get_resident_by_user_id(message.from_user.id)
resident = await residents_repository.get_resident_by_user_id(
message.from_user.id
)
if resident:
await dialog_manager.start(MainMenuSG.main, mode=StartMode.RESET_STACK)
return
@@ -9,15 +9,11 @@ class FloorsDAO:
self.session = session
async def get_by_id(self, floor_id: int) -> Floor | None:
result = await self.session.execute(
select(Floor).where(Floor.id == floor_id)
)
result = await self.session.execute(select(Floor).where(Floor.id == floor_id))
return result.scalar_one_or_none()
async def get_by_number(self, number: int) -> Floor | None:
result = await self.session.execute(
select(Floor).where(Floor.number == number)
)
result = await self.session.execute(select(Floor).where(Floor.number == number))
return result.scalar_one_or_none()
async def get_all(self) -> list[Floor]:
@@ -38,7 +34,5 @@ class FloorsDAO:
return await self.get_by_id(floor_id)
async def delete(self, floor_id: int) -> None:
await self.session.execute(
delete(Floor).where(Floor.id == floor_id)
)
await self.session.execute(delete(Floor).where(Floor.id == floor_id))
await self.session.commit()
@@ -50,7 +50,5 @@ class ResidentsDAO:
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.execute(delete(Resident).where(Resident.id == resident_id))
await self.session.commit()
@@ -9,15 +9,11 @@ class RoomsDAO:
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)
)
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)
)
result = await self.session.execute(select(Room).where(Room.number == number))
return result.scalar_one_or_none()
async def get_by_floor(self, floor_id: int) -> list[Room]:
@@ -44,7 +40,5 @@ class RoomsDAO:
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.execute(delete(Room).where(Room.id == room_id))
await self.session.commit()
@@ -9,9 +9,7 @@ class UsersDAO:
self.session = session
async def get_by_id(self, user_id: int) -> User | None:
result = await self.session.execute(
select(User).where(User.id == user_id)
)
result = await self.session.execute(select(User).where(User.id == user_id))
return result.scalar_one_or_none()
async def get_all(self) -> list[User]:
@@ -32,7 +30,5 @@ class UsersDAO:
return await self.get_by_id(user_id)
async def delete(self, user_id: int) -> None:
await self.session.execute(
delete(User).where(User.id == user_id)
)
await self.session.execute(delete(User).where(User.id == user_id))
await self.session.commit()
@@ -17,8 +17,14 @@ class HoursTransaction(Base):
__tablename__ = "hours_transactions"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
resident_id: Mapped[int] = mapped_column(Integer, ForeignKey("residents.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)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=msk_now)
admin_id: Mapped[int] = mapped_column(
BigInteger, ForeignKey("users.id", ondelete="SET NULL"), nullable=True
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=msk_now
)
@@ -12,10 +12,23 @@ class Resident(Base):
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")
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)
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
)
@@ -9,4 +9,6 @@ class Room(Base):
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
number: Mapped[int] = mapped_column(Integer, nullable=False, unique=True)
on_floor: Mapped[int] = mapped_column(Integer, ForeignKey("floors.id", ondelete="CASCADE"), nullable=False)
on_floor: Mapped[int] = mapped_column(
Integer, ForeignKey("floors.id", ondelete="CASCADE"), nullable=False
)
@@ -14,6 +14,12 @@ class User(Base):
username: Mapped[str | None] = mapped_column(String(255), nullable=True)
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")
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)
is_admin: Mapped[bool] = mapped_column(
Boolean, default=False, server_default="false"
)
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,6 +1,11 @@
from dutylog.infrastructure.database.dao.hours_transactions_dao import HoursTransactionsDAO
from dutylog.infrastructure.database.dao.hours_transactions_dao import (
HoursTransactionsDAO,
)
from dutylog.infrastructure.database.dao.residents_dao import ResidentsDAO
from dutylog.infrastructure.database.models.hours_transaction import HoursTransaction, TransactionType
from dutylog.infrastructure.database.models.hours_transaction import (
HoursTransaction,
TransactionType,
)
from dutylog.infrastructure.database.models.resident import Resident
@@ -32,10 +37,14 @@ class HoursTransactionsRepository:
if resident:
if is_active:
new_hours = resident.active_hours + amount
resident = await self.residents_dao.update(resident_id, active_hours=new_hours)
resident = await self.residents_dao.update(
resident_id, active_hours=new_hours
)
else:
new_hours = resident.inactive_hours + amount
resident = await self.residents_dao.update(resident_id, inactive_hours=new_hours)
resident = await self.residents_dao.update(
resident_id, inactive_hours=new_hours
)
return transaction, resident
@@ -58,10 +67,14 @@ class HoursTransactionsRepository:
if resident:
if is_active:
new_hours = max(0, resident.active_hours - amount)
resident = await self.residents_dao.update(resident_id, active_hours=new_hours)
resident = await self.residents_dao.update(
resident_id, active_hours=new_hours
)
else:
new_hours = max(0, resident.inactive_hours - amount)
resident = await self.residents_dao.update(resident_id, inactive_hours=new_hours)
resident = await self.residents_dao.update(
resident_id, inactive_hours=new_hours
)
return transaction, resident
@@ -71,5 +84,7 @@ class HoursTransactionsRepository:
async def get_all_transactions(self) -> list[HoursTransaction]:
return await self.transactions_dao.get_all()
async def get_transaction_by_id(self, transaction_id: int) -> HoursTransaction | None:
async def get_transaction_by_id(
self, transaction_id: int
) -> HoursTransaction | None:
return await self.transactions_dao.get_by_id(transaction_id)
@@ -17,7 +17,9 @@ class ResidentsRepository:
)
return await self.residents_dao.create(resident)
async def bind_user_to_resident(self, resident_id: int, user_id: int) -> Resident | None:
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,
@@ -52,7 +54,9 @@ class ResidentsRepository:
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 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:
+24 -13
View File
@@ -5,15 +5,27 @@ 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.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.dao.floors_dao import FloorsDAO
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.database.repositories.floors_repository import FloorsRepository
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.database.repositories.floors_repository import (
FloorsRepository,
)
from dutylog.infrastructure.utils.config import Config, load_config
@@ -29,7 +41,9 @@ class DatabaseProvider(Provider):
return create_engine(config.database.url)
@provide(scope=Scope.APP)
def get_session_maker(self, engine: AsyncEngine) -> async_sessionmaker[AsyncSession]:
def get_session_maker(
self, engine: AsyncEngine
) -> async_sessionmaker[AsyncSession]:
return create_session_maker(engine)
@provide(scope=Scope.REQUEST)
@@ -80,14 +94,11 @@ class RepositoryProvider(Provider):
return RoomsRepository(rooms_dao)
@provide(scope=Scope.REQUEST)
def get_residents_repository(self, residents_dao: ResidentsDAO) -> ResidentsRepository:
def get_residents_repository(
self, residents_dao: ResidentsDAO
) -> ResidentsRepository:
return ResidentsRepository(residents_dao)
@provide(scope=Scope.REQUEST)
def get_floors_repository(self, floors_dao: FloorsDAO) -> FloorsRepository:
return FloorsRepository(floors_dao)