mirror of
https://github.com/koloideal/DutyLog.git
synced 2026-06-10 10:25:29 +03:00
commit
This commit is contained in:
@@ -0,0 +1,74 @@
|
|||||||
|
"""add models
|
||||||
|
|
||||||
|
Revision ID: 0f74944ba3bb
|
||||||
|
Revises: fc1269fbc645
|
||||||
|
Create Date: 2026-02-28 09:17:21.827677
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '0f74944ba3bb'
|
||||||
|
down_revision: Union[str, Sequence[str], None] = 'fc1269fbc645'
|
||||||
|
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('floors',
|
||||||
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('number', sa.Integer(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('number')
|
||||||
|
)
|
||||||
|
op.create_table('rooms',
|
||||||
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('number', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('on_floor', sa.Integer(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['on_floor'], ['floors.id'], ondelete='CASCADE'),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('number')
|
||||||
|
)
|
||||||
|
op.create_table('residents',
|
||||||
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('real_name', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('room', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('user_entity', sa.BigInteger(), nullable=True),
|
||||||
|
sa.Column('is_busy', sa.Boolean(), server_default='false', nullable=False),
|
||||||
|
sa.Column('active_hours', sa.Integer(), server_default='0', nullable=False),
|
||||||
|
sa.Column('inactive_hours', sa.Integer(), server_default='0', nullable=False),
|
||||||
|
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
|
||||||
|
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['room'], ['rooms.id'], ondelete='CASCADE'),
|
||||||
|
sa.ForeignKeyConstraint(['user_entity'], ['users.id'], ondelete='SET NULL'),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('user_entity')
|
||||||
|
)
|
||||||
|
op.add_column('hours_transactions', sa.Column('resident_id', sa.Integer(), nullable=False))
|
||||||
|
op.drop_constraint(op.f('hours_transactions_user_id_fkey'), 'hours_transactions', type_='foreignkey')
|
||||||
|
op.create_foreign_key(None, 'hours_transactions', 'residents', ['resident_id'], ['id'], ondelete='CASCADE')
|
||||||
|
op.drop_column('hours_transactions', 'user_id')
|
||||||
|
op.drop_column('users', 'inactive_hours')
|
||||||
|
op.drop_column('users', 'active_hours')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('users', sa.Column('active_hours', sa.INTEGER(), server_default=sa.text('0'), autoincrement=False, nullable=False))
|
||||||
|
op.add_column('users', sa.Column('inactive_hours', sa.INTEGER(), server_default=sa.text('0'), autoincrement=False, nullable=False))
|
||||||
|
op.add_column('hours_transactions', sa.Column('user_id', sa.BIGINT(), autoincrement=False, nullable=False))
|
||||||
|
op.drop_constraint(None, 'hours_transactions', type_='foreignkey')
|
||||||
|
op.create_foreign_key(op.f('hours_transactions_user_id_fkey'), 'hours_transactions', 'users', ['user_id'], ['id'], ondelete='CASCADE')
|
||||||
|
op.drop_column('hours_transactions', 'resident_id')
|
||||||
|
op.drop_table('residents')
|
||||||
|
op.drop_table('rooms')
|
||||||
|
op.drop_table('floors')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
-- Массивы имен и фамилий для генерации
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
first_names TEXT[] := ARRAY['Aleksandr', 'Dmitriy', 'Maksim', 'Ivan', 'Artem', 'Mikhail', 'Daniil', 'Egor', 'Andrey', 'Nikita',
|
||||||
|
'Anna', 'Mariya', 'Elena', 'Olga', 'Ekaterina', 'Natalya', 'Tatyana', 'Irina', 'Yuliya', 'Svetlana'];
|
||||||
|
last_names TEXT[] := ARRAY['Ivanov', 'Petrov', 'Sidorov', 'Smirnov', 'Kuznetsov', 'Popov', 'Vasilev', 'Sokolov', 'Mikhaylov', 'Novikov',
|
||||||
|
'Fedorov', 'Morozov', 'Volkov', 'Alekseev', 'Lebedev', 'Semenov', 'Egorov', 'Pavlov', 'Kozlov', 'Stepanov'];
|
||||||
|
room_record RECORD;
|
||||||
|
residents_count INT;
|
||||||
|
i INT;
|
||||||
|
first_name TEXT;
|
||||||
|
last_name TEXT;
|
||||||
|
BEGIN
|
||||||
|
FOR room_record IN SELECT id FROM rooms LOOP
|
||||||
|
residents_count := 2 + floor(random() * 2)::INT;
|
||||||
|
|
||||||
|
FOR i IN 1..residents_count LOOP
|
||||||
|
first_name := first_names[1 + floor(random() * array_length(first_names, 1))::INT];
|
||||||
|
last_name := last_names[1 + floor(random() * array_length(last_names, 1))::INT];
|
||||||
|
|
||||||
|
INSERT INTO residents (real_name, room, is_busy, active_hours, inactive_hours, created_at, updated_at)
|
||||||
|
VALUES (first_name || ' ' || last_name, room_record.id, false, 0, 0, NOW(), NOW());
|
||||||
|
END LOOP;
|
||||||
|
END LOOP;
|
||||||
|
END $$;
|
||||||
@@ -11,6 +11,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.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
|
from dutylog.infrastructure.utils.config import load_config
|
||||||
|
|
||||||
@@ -24,6 +25,9 @@ async def main():
|
|||||||
token=config.bot.token,
|
token=config.bot.token,
|
||||||
default=DefaultBotProperties(parse_mode=ParseMode.HTML)
|
default=DefaultBotProperties(parse_mode=ParseMode.HTML)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await bot.delete_webhook(drop_pending_updates=True)
|
||||||
|
|
||||||
dp = Dispatcher()
|
dp = Dispatcher()
|
||||||
|
|
||||||
container = make_async_container(
|
container = make_async_container(
|
||||||
@@ -36,6 +40,7 @@ async def main():
|
|||||||
dp.include_router(user_router)
|
dp.include_router(user_router)
|
||||||
dp.include_router(main_menu_dialog)
|
dp.include_router(main_menu_dialog)
|
||||||
dp.include_router(admin_menu_dialog)
|
dp.include_router(admin_menu_dialog)
|
||||||
|
dp.include_router(registration_dialog)
|
||||||
|
|
||||||
setup_dialogs(dp)
|
setup_dialogs(dp)
|
||||||
setup_dishka(container, dp, auto_inject=True)
|
setup_dishka(container, dp, auto_inject=True)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from aiogram.types import User
|
from aiogram.types import User
|
||||||
from aiogram_dialog import Dialog, Window, DialogManager
|
from aiogram_dialog import Dialog, Window, DialogManager
|
||||||
from aiogram_dialog.widgets.text import Format, Const
|
from aiogram_dialog.widgets.text import Format, Const
|
||||||
from aiogram_dialog.widgets.kbd import SwitchTo, Back
|
from aiogram_dialog.widgets.kbd import SwitchTo, Back, Start
|
||||||
from dishka import FromDishka
|
from dishka import FromDishka
|
||||||
from dishka.integrations.aiogram_dialog import inject
|
from dishka.integrations.aiogram_dialog import inject
|
||||||
|
|
||||||
from dutylog.application.bot.user_dialogs.states import MainMenuSG
|
from dutylog.application.bot.user_dialogs.states import MainMenuSG, RegistrationSG
|
||||||
from dutylog.infrastructure.database.repositories.users_repository import UsersRepository
|
from dutylog.infrastructure.database.repositories.users_repository import UsersRepository
|
||||||
from dutylog.infrastructure.database.repositories.residents_repository import ResidentsRepository
|
from dutylog.infrastructure.database.repositories.residents_repository import ResidentsRepository
|
||||||
from dutylog.infrastructure.database.repositories.hours_transactions_repository import HoursTransactionsRepository
|
from dutylog.infrastructure.database.repositories.hours_transactions_repository import HoursTransactionsRepository
|
||||||
@@ -49,6 +49,7 @@ async def get_main_menu_data(
|
|||||||
Вы еще не привязаны к резиденту.
|
Вы еще не привязаны к резиденту.
|
||||||
Обратитесь к администратору для регистрации.
|
Обратитесь к администратору для регистрации.
|
||||||
"""
|
"""
|
||||||
|
has_resident = False
|
||||||
else:
|
else:
|
||||||
content = f"""
|
content = f"""
|
||||||
{greeting}
|
{greeting}
|
||||||
@@ -59,6 +60,7 @@ async def get_main_menu_data(
|
|||||||
━━━━━━━━━━━━━━━━
|
━━━━━━━━━━━━━━━━
|
||||||
🔴 Неотработанные часы: <code>{resident.inactive_hours}</code> ч</blockquote>
|
🔴 Неотработанные часы: <code>{resident.inactive_hours}</code> ч</blockquote>
|
||||||
"""
|
"""
|
||||||
|
has_resident = True
|
||||||
else:
|
else:
|
||||||
content = f"""
|
content = f"""
|
||||||
{greeting}
|
{greeting}
|
||||||
@@ -67,11 +69,12 @@ async def get_main_menu_data(
|
|||||||
|
|
||||||
Добро пожаловать в систему учета дежурств!
|
Добро пожаловать в систему учета дежурств!
|
||||||
"""
|
"""
|
||||||
|
has_resident = False
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"content": content,
|
"content": content,
|
||||||
"is_regular_user": not is_admin and not is_creator,
|
"is_regular_user": not is_admin and not is_creator,
|
||||||
"has_resident": resident is not None if not is_admin and not is_creator else False,
|
"has_resident": has_resident,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -128,6 +131,12 @@ main_menu_dialog = Dialog(
|
|||||||
state=MainMenuSG.history,
|
state=MainMenuSG.history,
|
||||||
when="has_resident",
|
when="has_resident",
|
||||||
),
|
),
|
||||||
|
Start(
|
||||||
|
Const("🔄 Перерегистрация"),
|
||||||
|
id="reregister_btn",
|
||||||
|
state=RegistrationSG.select_floor,
|
||||||
|
when="is_regular_user",
|
||||||
|
),
|
||||||
state=MainMenuSG.main,
|
state=MainMenuSG.main,
|
||||||
getter=get_main_menu_data,
|
getter=get_main_menu_data,
|
||||||
),
|
),
|
||||||
@@ -138,3 +147,4 @@ main_menu_dialog = Dialog(
|
|||||||
getter=get_history_data,
|
getter=get_history_data,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
from aiogram.types import CallbackQuery
|
||||||
|
from magic_filter import F
|
||||||
|
from aiogram_dialog import Dialog, Window, DialogManager
|
||||||
|
from aiogram_dialog.widgets.text import Format, Const
|
||||||
|
from aiogram_dialog.widgets.kbd import Select, Cancel, Group
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
@inject
|
||||||
|
async def get_floors_data(
|
||||||
|
floors_repository: FromDishka[FloorsRepository],
|
||||||
|
rooms_repository: FromDishka[RoomsRepository],
|
||||||
|
residents_repository: FromDishka[ResidentsRepository],
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
all_floors = await floors_repository.get_all_floors()
|
||||||
|
available_residents = await residents_repository.get_available_residents()
|
||||||
|
|
||||||
|
if not available_residents:
|
||||||
|
return {
|
||||||
|
"has_available": False,
|
||||||
|
"floors": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
available_room_ids = {r.room for r in available_residents}
|
||||||
|
all_rooms = await rooms_repository.get_all_rooms()
|
||||||
|
available_rooms = [r for r in all_rooms if r.id in available_room_ids]
|
||||||
|
available_floor_ids = {r.on_floor for r in available_rooms}
|
||||||
|
|
||||||
|
available_floors = [f for f in all_floors if f.id in available_floor_ids]
|
||||||
|
available_floors.sort(key=lambda f: f.number)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"has_available": True,
|
||||||
|
"floors": [(f.id, f"Этаж {f.number}") for f in available_floors],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@inject
|
||||||
|
async def get_rooms_data(
|
||||||
|
dialog_manager: DialogManager,
|
||||||
|
rooms_repository: FromDishka[RoomsRepository],
|
||||||
|
residents_repository: FromDishka[ResidentsRepository],
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
floor_id = dialog_manager.dialog_data["floor_id"]
|
||||||
|
|
||||||
|
rooms = await rooms_repository.get_rooms_by_floor(floor_id)
|
||||||
|
available_residents = await residents_repository.get_available_residents()
|
||||||
|
available_room_ids = {r.room for r in available_residents}
|
||||||
|
|
||||||
|
available_rooms = [r for r in rooms if r.id in available_room_ids]
|
||||||
|
available_rooms.sort(key=lambda r: r.number)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"rooms": [(r.id, str(r.number)) for r in available_rooms],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@inject
|
||||||
|
async def get_residents_data(
|
||||||
|
dialog_manager: DialogManager,
|
||||||
|
residents_repository: FromDishka[ResidentsRepository],
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
room_id = dialog_manager.dialog_data["room_id"]
|
||||||
|
|
||||||
|
residents = await residents_repository.get_residents_by_room(room_id)
|
||||||
|
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],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def on_floor_selected(
|
||||||
|
callback: CallbackQuery,
|
||||||
|
widget: Select,
|
||||||
|
dialog_manager: DialogManager,
|
||||||
|
floor_id: str,
|
||||||
|
):
|
||||||
|
dialog_manager.dialog_data["floor_id"] = int(floor_id)
|
||||||
|
await dialog_manager.switch_to(RegistrationSG.select_room)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_room_selected(
|
||||||
|
callback: CallbackQuery,
|
||||||
|
widget: Select,
|
||||||
|
dialog_manager: DialogManager,
|
||||||
|
room_id: str,
|
||||||
|
):
|
||||||
|
dialog_manager.dialog_data["room_id"] = int(room_id)
|
||||||
|
await dialog_manager.switch_to(RegistrationSG.select_resident)
|
||||||
|
|
||||||
|
|
||||||
|
@inject
|
||||||
|
async def on_resident_selected(
|
||||||
|
callback: CallbackQuery,
|
||||||
|
widget: Select,
|
||||||
|
dialog_manager: DialogManager,
|
||||||
|
item_id: str,
|
||||||
|
residents_repository: FromDishka[ResidentsRepository],
|
||||||
|
):
|
||||||
|
user_id = callback.from_user.id
|
||||||
|
resident_id = int(item_id)
|
||||||
|
|
||||||
|
await residents_repository.bind_user_to_resident(resident_id, user_id)
|
||||||
|
|
||||||
|
await callback.answer("✅ Вы успешно зарегистрированы!")
|
||||||
|
await dialog_manager.start(MainMenuSG.main)
|
||||||
|
|
||||||
|
|
||||||
|
registration_dialog = Dialog(
|
||||||
|
Window(
|
||||||
|
Const("<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]}"),
|
||||||
|
id="floor_select",
|
||||||
|
item_id_getter=lambda x: x[0],
|
||||||
|
items="floors",
|
||||||
|
on_click=on_floor_selected,
|
||||||
|
),
|
||||||
|
width=2,
|
||||||
|
when="has_available",
|
||||||
|
),
|
||||||
|
state=RegistrationSG.select_floor,
|
||||||
|
getter=get_floors_data,
|
||||||
|
),
|
||||||
|
Window(
|
||||||
|
Const("<blockquote>🚪 <b>Выбор комнаты</b></blockquote>\n\nВыберите вашу комнату:"),
|
||||||
|
Group(
|
||||||
|
Select(
|
||||||
|
Format("{item[1]}"),
|
||||||
|
id="room_select",
|
||||||
|
item_id_getter=lambda x: x[0],
|
||||||
|
items="rooms",
|
||||||
|
on_click=on_room_selected,
|
||||||
|
),
|
||||||
|
width=3,
|
||||||
|
),
|
||||||
|
Cancel(Const("◀️ Назад")),
|
||||||
|
state=RegistrationSG.select_room,
|
||||||
|
getter=get_rooms_data,
|
||||||
|
),
|
||||||
|
Window(
|
||||||
|
Const("<blockquote>👤 <b>Выбор резидента</b></blockquote>\n\nВыберите себя из списка:"),
|
||||||
|
Group(
|
||||||
|
Select(
|
||||||
|
Format("{item[1]}"),
|
||||||
|
id="resident_select",
|
||||||
|
item_id_getter=lambda x: x[0],
|
||||||
|
items="residents",
|
||||||
|
on_click=on_resident_selected,
|
||||||
|
),
|
||||||
|
width=1,
|
||||||
|
),
|
||||||
|
Cancel(Const("◀️ Назад")),
|
||||||
|
state=RegistrationSG.select_resident,
|
||||||
|
getter=get_residents_data,
|
||||||
|
),
|
||||||
|
)
|
||||||
@@ -11,3 +11,9 @@ class AdminMenuSG(StatesGroup):
|
|||||||
users_list = State()
|
users_list = State()
|
||||||
statistics = State()
|
statistics = State()
|
||||||
broadcast = State()
|
broadcast = State()
|
||||||
|
|
||||||
|
|
||||||
|
class RegistrationSG(StatesGroup):
|
||||||
|
select_floor = State()
|
||||||
|
select_room = State()
|
||||||
|
select_resident = State()
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ from aiogram.types import Message
|
|||||||
from aiogram_dialog import DialogManager, StartMode
|
from aiogram_dialog import DialogManager, StartMode
|
||||||
from dishka import FromDishka
|
from dishka import FromDishka
|
||||||
|
|
||||||
from dutylog.application.bot.user_dialogs.states import MainMenuSG, AdminMenuSG
|
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.users_repository import UsersRepository
|
||||||
|
from dutylog.infrastructure.database.repositories.residents_repository import ResidentsRepository
|
||||||
from dutylog.infrastructure.utils.config import Config
|
from dutylog.infrastructure.utils.config import Config
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
@@ -16,20 +17,23 @@ async def start_handler(
|
|||||||
message: Message,
|
message: Message,
|
||||||
dialog_manager: DialogManager,
|
dialog_manager: DialogManager,
|
||||||
users_repository: FromDishka[UsersRepository],
|
users_repository: FromDishka[UsersRepository],
|
||||||
|
residents_repository: FromDishka[ResidentsRepository],
|
||||||
config: FromDishka[Config],
|
config: FromDishka[Config],
|
||||||
):
|
):
|
||||||
assert message.from_user is not None
|
assert message.from_user is not None
|
||||||
user = await users_repository.get_or_create_user(
|
user = await users_repository.get_user_by_id(message.from_user.id)
|
||||||
user_id=message.from_user.id,
|
|
||||||
username=message.from_user.username,
|
|
||||||
first_name=message.from_user.first_name,
|
|
||||||
last_name=message.from_user.last_name,
|
|
||||||
)
|
|
||||||
|
|
||||||
is_creator = message.from_user.id == config.bot.creator_id
|
is_creator = message.from_user.id == config.bot.creator_id
|
||||||
is_admin = user.is_admin
|
is_admin = user.is_admin if user else False
|
||||||
|
|
||||||
if is_admin or is_creator:
|
if is_admin or is_creator:
|
||||||
await dialog_manager.start(AdminMenuSG.main, mode=StartMode.RESET_STACK)
|
await dialog_manager.start(AdminMenuSG.main, mode=StartMode.RESET_STACK)
|
||||||
else:
|
return
|
||||||
|
|
||||||
|
if user:
|
||||||
|
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)
|
await dialog_manager.start(MainMenuSG.main, mode=StartMode.RESET_STACK)
|
||||||
|
return
|
||||||
|
|
||||||
|
await dialog_manager.start(RegistrationSG.select_floor, mode=StartMode.RESET_STACK)
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
from sqlalchemy import select, update, delete
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from dutylog.infrastructure.database.models.floor import Floor
|
||||||
|
|
||||||
|
|
||||||
|
class FloorsDAO:
|
||||||
|
def __init__(self, session: AsyncSession):
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
return result.scalar_one_or_none()
|
||||||
|
|
||||||
|
async def get_all(self) -> list[Floor]:
|
||||||
|
result = await self.session.execute(select(Floor))
|
||||||
|
return list(result.scalars().all())
|
||||||
|
|
||||||
|
async def create(self, floor: Floor) -> Floor:
|
||||||
|
self.session.add(floor)
|
||||||
|
await self.session.commit()
|
||||||
|
await self.session.refresh(floor)
|
||||||
|
return floor
|
||||||
|
|
||||||
|
async def update(self, floor_id: int, **kwargs) -> Floor | None:
|
||||||
|
await self.session.execute(
|
||||||
|
update(Floor).where(Floor.id == floor_id).values(**kwargs)
|
||||||
|
)
|
||||||
|
await self.session.commit()
|
||||||
|
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.commit()
|
||||||
@@ -20,6 +20,12 @@ class RoomsDAO:
|
|||||||
)
|
)
|
||||||
return result.scalar_one_or_none()
|
return result.scalar_one_or_none()
|
||||||
|
|
||||||
|
async def get_by_floor(self, floor_id: int) -> list[Room]:
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(Room).where(Room.on_floor == floor_id)
|
||||||
|
)
|
||||||
|
return list(result.scalars().all())
|
||||||
|
|
||||||
async def get_all(self) -> list[Room]:
|
async def get_all(self) -> list[Room]:
|
||||||
result = await self.session.execute(select(Room))
|
result = await self.session.execute(select(Room))
|
||||||
return list(result.scalars().all())
|
return list(result.scalars().all())
|
||||||
|
|||||||
@@ -3,5 +3,6 @@ from dutylog.infrastructure.database.models.user import User
|
|||||||
from dutylog.infrastructure.database.models.hours_transaction import HoursTransaction
|
from dutylog.infrastructure.database.models.hours_transaction import HoursTransaction
|
||||||
from dutylog.infrastructure.database.models.room import Room
|
from dutylog.infrastructure.database.models.room import Room
|
||||||
from dutylog.infrastructure.database.models.resident import Resident
|
from dutylog.infrastructure.database.models.resident import Resident
|
||||||
|
from dutylog.infrastructure.database.models.floor import Floor
|
||||||
|
|
||||||
__all__ = ["Base", "User", "HoursTransaction", "Room", "Resident"]
|
__all__ = ["Base", "User", "HoursTransaction", "Room", "Resident", "Floor"]
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from sqlalchemy import Integer
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
|
from dutylog.infrastructure.database.models.base import Base
|
||||||
|
|
||||||
|
|
||||||
|
class Floor(Base):
|
||||||
|
__tablename__ = "floors"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
number: Mapped[int] = mapped_column(Integer, nullable=False, unique=True)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from sqlalchemy import Integer
|
from sqlalchemy import Integer, ForeignKey
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
from dutylog.infrastructure.database.models.base import Base
|
from dutylog.infrastructure.database.models.base import Base
|
||||||
@@ -9,3 +9,4 @@ class Room(Base):
|
|||||||
|
|
||||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||||
number: Mapped[int] = mapped_column(Integer, nullable=False, unique=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)
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
from dutylog.infrastructure.database.dao.floors_dao import FloorsDAO
|
||||||
|
from dutylog.infrastructure.database.models.floor import Floor
|
||||||
|
|
||||||
|
|
||||||
|
class FloorsRepository:
|
||||||
|
def __init__(self, floors_dao: FloorsDAO):
|
||||||
|
self.floors_dao = floors_dao
|
||||||
|
|
||||||
|
async def get_or_create_floor(self, number: int) -> Floor:
|
||||||
|
floor = await self.floors_dao.get_by_number(number)
|
||||||
|
if not floor:
|
||||||
|
floor = Floor(number=number)
|
||||||
|
floor = await self.floors_dao.create(floor)
|
||||||
|
return floor
|
||||||
|
|
||||||
|
async def get_floor_by_id(self, floor_id: int) -> Floor | None:
|
||||||
|
return await self.floors_dao.get_by_id(floor_id)
|
||||||
|
|
||||||
|
async def get_floor_by_number(self, number: int) -> Floor | None:
|
||||||
|
return await self.floors_dao.get_by_number(number)
|
||||||
|
|
||||||
|
async def get_all_floors(self) -> list[Floor]:
|
||||||
|
return await self.floors_dao.get_all()
|
||||||
|
|
||||||
|
async def delete_floor(self, floor_id: int) -> None:
|
||||||
|
await self.floors_dao.delete(floor_id)
|
||||||
@@ -6,12 +6,9 @@ class RoomsRepository:
|
|||||||
def __init__(self, rooms_dao: RoomsDAO):
|
def __init__(self, rooms_dao: RoomsDAO):
|
||||||
self.rooms_dao = rooms_dao
|
self.rooms_dao = rooms_dao
|
||||||
|
|
||||||
async def get_or_create_room(self, number: int) -> Room:
|
async def create_room(self, number: int, floor_id: int) -> Room:
|
||||||
room = await self.rooms_dao.get_by_number(number)
|
room = Room(number=number, on_floor=floor_id)
|
||||||
if not room:
|
return await self.rooms_dao.create(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:
|
async def get_room_by_id(self, room_id: int) -> Room | None:
|
||||||
return await self.rooms_dao.get_by_id(room_id)
|
return await self.rooms_dao.get_by_id(room_id)
|
||||||
@@ -19,6 +16,9 @@ class RoomsRepository:
|
|||||||
async def get_room_by_number(self, number: int) -> Room | None:
|
async def get_room_by_number(self, number: int) -> Room | None:
|
||||||
return await self.rooms_dao.get_by_number(number)
|
return await self.rooms_dao.get_by_number(number)
|
||||||
|
|
||||||
|
async def get_rooms_by_floor(self, floor_id: int) -> list[Room]:
|
||||||
|
return await self.rooms_dao.get_by_floor(floor_id)
|
||||||
|
|
||||||
async def get_all_rooms(self) -> list[Room]:
|
async def get_all_rooms(self) -> list[Room]:
|
||||||
return await self.rooms_dao.get_all()
|
return await self.rooms_dao.get_all()
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ 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.rooms_dao import RoomsDAO
|
||||||
from dutylog.infrastructure.database.dao.residents_dao import ResidentsDAO
|
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.users_repository import UsersRepository
|
||||||
from dutylog.infrastructure.database.repositories.hours_transactions_repository import HoursTransactionsRepository
|
from dutylog.infrastructure.database.repositories.hours_transactions_repository import HoursTransactionsRepository
|
||||||
from dutylog.infrastructure.database.repositories.rooms_repository import RoomsRepository
|
from dutylog.infrastructure.database.repositories.rooms_repository import RoomsRepository
|
||||||
from dutylog.infrastructure.database.repositories.residents_repository import ResidentsRepository
|
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
|
from dutylog.infrastructure.utils.config import Config, load_config
|
||||||
|
|
||||||
|
|
||||||
@@ -55,6 +57,10 @@ class DAOProvider(Provider):
|
|||||||
def get_residents_dao(self, session: AsyncSession) -> ResidentsDAO:
|
def get_residents_dao(self, session: AsyncSession) -> ResidentsDAO:
|
||||||
return ResidentsDAO(session)
|
return ResidentsDAO(session)
|
||||||
|
|
||||||
|
@provide(scope=Scope.REQUEST)
|
||||||
|
def get_floors_dao(self, session: AsyncSession) -> FloorsDAO:
|
||||||
|
return FloorsDAO(session)
|
||||||
|
|
||||||
|
|
||||||
class RepositoryProvider(Provider):
|
class RepositoryProvider(Provider):
|
||||||
@provide(scope=Scope.REQUEST)
|
@provide(scope=Scope.REQUEST)
|
||||||
@@ -77,6 +83,11 @@ class RepositoryProvider(Provider):
|
|||||||
def get_residents_repository(self, residents_dao: ResidentsDAO) -> ResidentsRepository:
|
def get_residents_repository(self, residents_dao: ResidentsDAO) -> ResidentsRepository:
|
||||||
return ResidentsRepository(residents_dao)
|
return ResidentsRepository(residents_dao)
|
||||||
|
|
||||||
|
@provide(scope=Scope.REQUEST)
|
||||||
|
def get_floors_repository(self, floors_dao: FloorsDAO) -> FloorsRepository:
|
||||||
|
return FloorsRepository(floors_dao)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user