diff --git a/import_hours.py b/import_hours.py
new file mode 100644
index 0000000..87bbd94
--- /dev/null
+++ b/import_hours.py
@@ -0,0 +1,251 @@
+import argparse
+import asyncio
+import pandas as pd
+import asyncpg
+import re
+import os
+
+
+def clean_name(name: str) -> str:
+ if not isinstance(name, str):
+ return ""
+ name = re.sub(r'\s+[СC]$', '', name.strip())
+ name = re.sub(r'\s*\d+\s*$', '', name)
+ return name.strip()
+
+
+def parse_excel_raw(file_path: str):
+ excel_file = pd.ExcelFile(file_path)
+ resident_hours = {}
+ room_hours = {}
+
+ for sheet_name in excel_file.sheet_names:
+ df = pd.read_excel(file_path, sheet_name=sheet_name)
+
+ # --- Парсинг жильцов ---
+ if 'ФИО' in df.columns and 'Часы' in df.columns:
+ for _, row in df.iterrows():
+ fio = clean_name(str(row['ФИО']))
+ if not fio or str(fio).lower() == 'nan':
+ continue
+ try:
+ hours_raw = str(row['Часы']).split('(')[0].strip()
+ amount = int(float(hours_raw))
+ except ValueError:
+ continue
+
+ if fio in resident_hours:
+ resident_hours[fio] += amount
+ else:
+ resident_hours[fio] = amount
+
+ # --- Парсинг комнат ---
+ elif df.iloc[0].astype(str).str.contains('Комната', case=False, na=False).any():
+ header_idx = df[df.apply(lambda r: r.astype(str).str.contains('Комната', case=False).any(), axis=1)].index[0]
+ df.columns = df.iloc[header_idx]
+ df = df.iloc[header_idx + 1:].reset_index(drop=True)
+
+ for _, row in df.iterrows():
+ room_str = str(row['Комната'])
+ if not room_str or room_str.lower() == 'nan':
+ continue
+
+ room_match = re.search(r'\b(\d{3,4})\b', room_str)
+ if not room_match:
+ continue
+
+ room_number = int(room_match.group(1))
+
+ try:
+ punishment_raw = str(row['Наказание']).split('(')[0].replace('часов', '').replace(',', ';').strip()
+ amounts = [int(x.strip()) for x in re.findall(r'\d+', punishment_raw)]
+ amount = sum(amounts) if amounts else 0
+ except (ValueError, AttributeError):
+ continue
+
+ if amount <= 0:
+ continue
+
+ if room_number in room_hours:
+ room_hours[room_number] += amount
+ else:
+ room_hours[room_number] = amount
+
+ return resident_hours, room_hours
+
+
+async def run_parse(database_url: str, file_path: str):
+ raw_resident_hours, raw_room_hours = parse_excel_raw(file_path)
+
+ async with asyncpg.create_pool(database_url) as pool:
+ async with pool.acquire() as conn:
+ resident_rows = await conn.fetch("SELECT real_name FROM residents WHERE real_name IS NOT NULL;")
+ db_names = [row['real_name'] for row in resident_rows]
+
+ room_rows = await conn.fetch("SELECT number FROM rooms;")
+ db_rooms = {row['number'] for row in room_rows}
+
+ # --- Сопоставление жильцов ---
+ db_names_lower = {name.lower(): name for name in db_names}
+ matched_residents = {}
+ unmatched_residents = {}
+
+ for raw_fio, amount in raw_resident_hours.items():
+ fio_lower = raw_fio.lower()
+ matched_name = None
+
+ if fio_lower in db_names_lower:
+ matched_name = db_names_lower[fio_lower]
+ else:
+ parts = fio_lower.split()
+ if len(parts) >= 2:
+ search_key = f"{parts[0]} {parts[1]}"
+ if search_key in db_names_lower:
+ matched_name = db_names_lower[search_key]
+
+ if not matched_name:
+ for db_lower, db_orig in db_names_lower.items():
+ if fio_lower in db_lower or db_lower in fio_lower:
+ matched_name = db_orig
+ break
+
+ if matched_name:
+ matched_residents[matched_name] = matched_residents.get(matched_name, 0) + amount
+ else:
+ unmatched_residents[raw_fio] = unmatched_residents.get(raw_fio, 0) + amount
+
+ # --- Сопоставление комнат ---
+ matched_rooms = {}
+ unmatched_rooms = {}
+
+ for room_num, amount in raw_room_hours.items():
+ if room_num in db_rooms:
+ matched_rooms[room_num] = matched_rooms.get(room_num, 0) + amount
+ else:
+ unmatched_rooms[room_num] = unmatched_rooms.get(room_num, 0) + amount
+
+ # --- Запись в файлы ---
+ '''with open("parsed_residents.txt", "w", encoding="utf-8") as f:
+ for name, amount in sorted(matched_residents.items()):
+ f.write(f"{name} - {amount}\n")'''
+
+ with open("parsed_rooms.txt", "w", encoding="utf-8") as f:
+ for room, amount in sorted(matched_rooms.items()):
+ f.write(f"{room} - {amount}\n")
+
+ '''if unmatched_residents:
+ with open("unmatched_residents.txt", "w", encoding="utf-8") as f:
+ for name, amount in sorted(unmatched_residents.items()):
+ f.write(f"{name} - {amount}\n")'''
+
+ if unmatched_rooms:
+ with open("unmatched_rooms.txt", "w", encoding="utf-8") as f:
+ for room, amount in sorted(unmatched_rooms.items()):
+ f.write(f"{room} - {amount}\n")
+
+
+async def run_load(database_url: str, admin_id: int):
+ if not os.path.exists("parsed_residents.txt") or not os.path.exists("parsed_rooms.txt"):
+ raise FileNotFoundError("Файлы parsed_residents.txt или parsed_rooms.txt не найдены. Сначала запустите parse.")
+
+ resident_transactions = []
+ with open("parsed_residents.txt", "r", encoding="utf-8") as f:
+ for line in f:
+ line = line.strip()
+ if not line:
+ continue
+ parts = line.split(" - ")
+ if len(parts) == 2:
+ resident_transactions.append((parts[0].strip(), int(parts[1].strip())))
+
+ room_transactions = []
+ with open("parsed_rooms.txt", "r", encoding="utf-8") as f:
+ for line in f:
+ line = line.strip()
+ if not line:
+ continue
+ parts = line.split(" - ")
+ if len(parts) == 2:
+ room_transactions.append((int(parts[0].strip()), int(parts[1].strip())))
+
+ async with asyncpg.create_pool(database_url) as pool:
+ async with pool.acquire() as conn:
+ # Маппинги
+ res_rows = await conn.fetch("SELECT id, real_name FROM residents WHERE real_name IS NOT NULL;")
+ name_to_id = {row['real_name']: row['id'] for row in res_rows}
+
+ rm_rows = await conn.fetch("SELECT id, number FROM rooms;")
+ num_to_id = {row['number']: row['id'] for row in rm_rows}
+
+ res_tx_params = []
+ res_update_params = []
+ for name, amount in resident_transactions:
+ res_id = name_to_id.get(name)
+ if res_id:
+ res_tx_params.append((res_id, "increase", amount, admin_id, "Инициализация существующих часов"))
+ res_update_params.append((amount, res_id))
+
+ rm_tx_params = []
+ rm_update_params = []
+ for room_num, amount in room_transactions:
+ rm_id = num_to_id.get(room_num)
+ if rm_id:
+ rm_tx_params.append((rm_id, "increase", amount, admin_id, "Инициализация существующих часов"))
+ rm_update_params.append((amount, rm_id))
+
+ async with conn.transaction():
+ # Транзакции резидентов
+ if res_tx_params:
+ await conn.executemany(
+ """
+ INSERT INTO hours_transactions
+ (resident_id, transaction_type, amount, admin_id, remark, created_at)
+ VALUES ($1, $2, $3, $4, $5, CURRENT_TIMESTAMP);
+ """, res_tx_params
+ )
+ # Обновление баланса резидентов
+ await conn.executemany(
+ """
+ UPDATE residents
+ SET active_hours = active_hours + $1, updated_at = CURRENT_TIMESTAMP
+ WHERE id = $2;
+ """, res_update_params
+ )
+
+ # Транзакции комнат
+ if rm_tx_params:
+ await conn.executemany(
+ """
+ INSERT INTO room_hours_transactions
+ (room_id, transaction_type, amount, admin_id, remark, created_at)
+ VALUES ($1, $2, $3, $4, $5, CURRENT_TIMESTAMP);
+ """, rm_tx_params
+ )
+ # Обновление баланса комнат
+ await conn.executemany(
+ """
+ UPDATE rooms
+ SET active_hours = active_hours + $1
+ WHERE id = $2;
+ """, rm_update_params
+ )
+
+
+async def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("command", choices=["parse", "load"])
+ parser.add_argument("--db-url", required=True)
+ parser.add_argument("--file", required=False)
+ parser.add_argument("--admin-id", type=int, default=2047958833)
+ args = parser.parse_args()
+
+ if args.command == "parse":
+ if not args.file:
+ parser.error("Для команды parse требуется указать --file")
+ await run_parse(args.db_url, args.file)
+ elif args.command == "load":
+ await run_load(args.db_url, args.admin_id)
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/import_residents.py b/import_residents.py
new file mode 100644
index 0000000..2d3c74d
--- /dev/null
+++ b/import_residents.py
@@ -0,0 +1,102 @@
+import argparse
+import asyncio
+import re
+from docx import Document
+import asyncpg
+
+
+def parse_residents(docx_path: str) -> list[dict]:
+ doc = Document(docx_path)
+ results = []
+ seen = set()
+
+ for table in doc.tables:
+ current_room = None
+ for row in table.rows:
+ cells = row.cells
+ if len(cells) < 3:
+ continue
+
+ cell0_text = cells[0].text.strip()
+ room_match = re.search(r'\b(\d{3,4})\b', cell0_text)
+ if room_match:
+ current_room = int(room_match.group(1))
+
+ name_text = cells[2].text.strip()
+ skip_keywords = ('Дата', 'Фамилия', 'Осталось', 'Совершеннолетних')
+
+ if name_text and current_room and not any(kw in name_text for kw in skip_keywords):
+ clean_name = re.sub(r'\s+[СC]$', '', name_text).strip()
+ if clean_name and len(clean_name) > 2:
+ key = (current_room, clean_name)
+ if key not in seen:
+ seen.add(key)
+ results.append({"room_number": current_room, "real_name": clean_name})
+
+ return results
+
+
+async def insert_data(database_url: str, residents: list[dict]) -> None:
+ async with asyncpg.create_pool(database_url) as pool:
+ async with pool.acquire() as conn:
+ async with conn.transaction():
+ floor_rows = await conn.fetch("SELECT id, number FROM floors;")
+ floor_map = {row['number']: row['id'] for row in floor_rows}
+
+ if not floor_map:
+ raise RuntimeError("Таблица floors пуста, невозможно привязать комнаты.")
+
+ unique_rooms = set(r["room_number"] for r in residents)
+ room_insert_params = []
+
+ for room_num in unique_rooms:
+ floor_num = room_num // 100
+ if floor_num not in floor_map:
+ raise ValueError(f"Для комнаты {room_num} не найден этаж {floor_num} в БД.")
+ room_insert_params.append((room_num, floor_map[floor_num]))
+
+ await conn.executemany(
+ """
+ INSERT INTO rooms (number, on_floor)
+ VALUES ($1, $2)
+ ON CONFLICT (number) DO NOTHING;
+ """,
+ room_insert_params
+ )
+
+ room_rows = await conn.fetch(
+ """
+ SELECT id, number
+ FROM rooms
+ WHERE number = ANY($1::int[]);
+ """,
+ list(unique_rooms)
+ )
+ room_map = {row['number']: row['id'] for row in room_rows}
+
+ resident_params = [
+ (r["real_name"], room_map[r["room_number"]])
+ for r in residents
+ ]
+
+ await conn.executemany(
+ """
+ INSERT INTO residents (real_name, room, created_at, updated_at)
+ VALUES ($1, $2, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
+ """,
+ resident_params
+ )
+
+
+async def main() -> None:
+ parser = argparse.ArgumentParser(description="Импорт жильцов из docx в PostgreSQL")
+ parser.add_argument("--db-url", required=True, help="URL базы данных (напр. postgresql://user:pass@localhost/db)")
+ parser.add_argument("--file", required=True, help="Путь к docx файлу")
+ args = parser.parse_args()
+
+ residents = parse_residents(args.file)
+ await insert_data(args.db_url, residents)
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/init_floors_rooms.sql b/init_floors_rooms.sql
deleted file mode 100644
index a781c05..0000000
--- a/init_floors_rooms.sql
+++ /dev/null
@@ -1,35 +0,0 @@
-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'];
- floor_num INT;
- floor_id INT;
- room_num INT;
- room_id INT;
- residents_count INT;
- i INT;
- first_name TEXT;
- last_name TEXT;
-BEGIN
- FOR floor_num IN 2..5 LOOP
- INSERT INTO floors (number) VALUES (floor_num) RETURNING id INTO floor_id;
-
- FOR room_num IN 1..15 LOOP
- INSERT INTO rooms (number, on_floor)
- VALUES (floor_num * 100 + room_num, floor_id)
- RETURNING id INTO room_id;
-
- 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_id, false, 0, 0, NOW(), NOW());
- END LOOP;
- END LOOP;
- END LOOP;
-END $$;
diff --git a/parsed_residents.txt b/parsed_residents.txt
new file mode 100644
index 0000000..3561c0d
--- /dev/null
+++ b/parsed_residents.txt
@@ -0,0 +1,98 @@
+Абакунчик Анастасия - 55
+Адамович Анастасия - 30
+Алиева Ангелина - 5
+Анищенок Алена - 5
+Апацкая Александра - 25
+Басаревский Олег - 50
+Бобкова Софья - 60
+Бойко Виктория - 55
+Буйвол Екатерина - 45
+Вишневская Вирсавия - 20
+Володько Виктория - 10
+Гвоздович Иван - 45
+Гинцева Ева - 5
+Гумовский Тимур - 20
+Гутырчик Арина - 5
+Гутырчик Никита - 5
+Давлетшина Алеся - 35
+Доморацкий Ярослав - 105
+Дубинец Ксения - 20
+Дудинская Полина - 15
+Демова Маргарита - 10
+Дауд Фэрдос - 25
+Жилевич Кира - 15
+Зимницкая Валерия - 10
+Игнатенко Маргарита - 10
+Илюкевич Анна - 10
+Казак Милана - 10
+Канорин Егор - 75
+Каранько Дарья - 30
+Климбовский Иван - 55
+Костюкевич Лиза - 5
+Клименко Дарья - 15
+Климович Дмитрий - 30
+Ковалева Анна - 45
+Крукович Маргарита - 10
+Кулага Софья - 65
+Левизанова Алина - 10
+Лемеза Надежда - 55
+Липницкая Ксения - 45
+Лисица Наталья - 45
+Лобановская Кристина - 10
+Логинова Мария - 10
+Лось Артем - 85
+Лукьянов Павел - 15
+Ляхнович Яна - 20
+Максимова Юлия - 60
+Мальков Артем - 35
+Мамонько Анна - 80
+Мананкова Дарья - 90
+Мануйлов Артем - 65
+Мысливец Екатерина - 5
+Марьянский Дмитрий - 10
+Мельник Кира - 5
+Маковский Даниил - 35
+Мирошниченко Максим - 5
+Михаленок Полина - 15
+Мусаева Дильшад - 50
+Нагорная Марина - 10
+Неверовская Майя - 90
+Негода Никита - 40
+Немченко Виктория - 10
+Новикова Ксения - 80
+Островская Валерия - 10
+Павлусенко Богдан - 10
+Понамарева Алина - 10
+Пашкевич Николай - 60
+Пожиган Анастасия - 20
+Пусторжевцева Виктория - 10
+Рабцевич Дмитрий - 10
+Саланович Анастасия - 15
+Сергей Ангелина - 15
+Сечко Алина - 15
+Сивакова Татьяна - 5
+Скринник Маргарита - 35
+Сманцер Алесь - 75
+Скурьят Юлиана - 5
+Семенов Даниил - 5
+Спиченок Кира - 15
+Салоха Полина - 5
+Сушкевич Артем - 30
+Сырокваш Валерия - 5
+Терехова Арина - 15
+Тозик Константин - 5
+Тризнюк Станислав - 15
+Трухан Яна - 35
+Турончик Ангелина - 35
+Тюнис Надежда - 15
+Хацкевич Дарья - 5
+Цыбулько Екатерина - 10
+Черник Дарья - 5
+Чигир Дарья - 220
+Шарапова Ксения - 20
+Шидловский Георгий - 10
+Шишкина София - 25
+Щелоков Тимофей - 25
+Юхович Дарья - 105
+Янущик Владислав - 55
+Зимницкая Эвелина - 5
diff --git a/parsed_rooms.txt b/parsed_rooms.txt
new file mode 100644
index 0000000..90c17d3
--- /dev/null
+++ b/parsed_rooms.txt
@@ -0,0 +1,44 @@
+201 - 5
+202 - 20
+203 - 5
+206 - 5
+207 - 10
+208 - 5
+209 - 5
+210 - 15
+211 - 10
+212 - 10
+213 - 5
+214 - 10
+215 - 55
+216 - 10
+301 - 5
+302 - 10
+303 - 35
+304 - 25
+305 - 15
+308 - 10
+310 - 15
+311 - 10
+313 - 5
+314 - 5
+315 - 20
+402 - 5
+403 - 5
+404 - 5
+405 - 10
+408 - 10
+412 - 10
+415 - 10
+501 - 10
+506 - 40
+507 - 20
+508 - 5
+509 - 20
+510 - 50
+511 - 100
+512 - 5
+513 - 15
+514 - 45
+515 - 15
+517 - 15
diff --git a/seed_rooms.py b/seed_rooms.py
new file mode 100644
index 0000000..2268362
--- /dev/null
+++ b/seed_rooms.py
@@ -0,0 +1,57 @@
+import argparse
+import asyncio
+import asyncpg
+
+
+async def seed_floors_and_rooms(database_url: str):
+ async with asyncpg.create_pool(database_url) as pool:
+ async with pool.acquire() as conn:
+ async with conn.transaction():
+
+ # Создаем этажи (со 2-го по 5-й)
+ floors = [2, 3, 4, 5]
+ await conn.executemany(
+ """
+ INSERT INTO floors (number)
+ VALUES ($1)
+ ON CONFLICT (number) DO NOTHING;
+ """,
+ [(f,) for f in floors]
+ )
+ print(f"Добавлены или уже существуют этажи: {floors}")
+
+ # Получаем ID созданных этажей
+ floor_rows = await conn.fetch("SELECT id, number FROM floors WHERE number = ANY($1::int[]);", floors)
+ floor_map = {row['number']: row['id'] for row in floor_rows}
+
+ # Создаем комнаты (x01 .. x16 для каждого этажа)
+ rooms_to_insert = []
+ for floor_num in floors:
+ floor_id = floor_map[floor_num]
+ for room_suffix in range(1, 17):
+ room_number = floor_num * 100 + room_suffix # 201, 202, ..., 516
+ rooms_to_insert.append((room_number, floor_id))
+
+ # Выполняем вставку комнат
+ await conn.executemany(
+ """
+ INSERT INTO rooms (number, on_floor)
+ VALUES ($1, $2)
+ ON CONFLICT (number) DO NOTHING;
+ """,
+ rooms_to_insert
+ )
+ print(f"Добавлены или уже существуют {len(rooms_to_insert)} комнат.")
+
+
+async def main():
+ parser = argparse.ArgumentParser(description="Инициализация этажей (2-5) и комнат (x01-x16)")
+ parser.add_argument("--db-url", required=True, help="URL базы данных")
+ args = parser.parse_args()
+
+ await seed_floors_and_rooms(args.db_url)
+ print("Инициализация завершена.")
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/src/dutylog/application/bot/admin_dialogs/residents_management.py b/src/dutylog/application/bot/admin_dialogs/residents_management.py
index e7d8b7d..8dbf773 100644
--- a/src/dutylog/application/bot/admin_dialogs/residents_management.py
+++ b/src/dutylog/application/bot/admin_dialogs/residents_management.py
@@ -27,6 +27,7 @@ async def get_residents_list_data(
residents_repository: FromDishka[ResidentsRepository],
rooms_repository: FromDishka[RoomsRepository],
floors_repository: FromDishka[FloorsRepository],
+ users_repository: FromDishka[UsersRepository],
**kwargs,
):
all_residents = await residents_repository.get_all_residents()
@@ -41,17 +42,25 @@ async def get_residents_list_data(
else:
floor_number = 999999
room_number = 999999
- residents_with_rooms.append((resident, floor_number, room_number))
+
+ is_admin = False
+ if resident.user_entity:
+ user = await users_repository.get_user_by_id(resident.user_entity)
+ if user and user.is_admin:
+ is_admin = True
+
+ residents_with_rooms.append((resident, floor_number, room_number, is_admin))
residents_with_rooms.sort(key=lambda x: (x[1], x[2]))
residents_data = []
- for resident, floor_number, room_number in residents_with_rooms:
+ for resident, floor_number, room_number, is_admin in residents_with_rooms:
status = "🟢" if resident.is_busy else "⚪️"
name = resident.real_name if resident.real_name else "Без имени"
+ admin_badge = " 👑" if is_admin else ""
residents_data.append(
- (f"{name} | Комната {room_number} | {status}", resident.id)
+ (f"{name} | Комната {room_number} | {status}{admin_badge}", resident.id)
)
content = f"""
@@ -79,12 +88,12 @@ async def get_resident_info_data(
resident_id = dialog_manager.dialog_data.get("selected_resident_id")
if not resident_id:
- return {"info_content": "Ошибка: резидент не выбран", "is_busy": False, "from_search": False, "from_filter": False}
+ return {"info_content": "Ошибка: резидент не выбран", "is_busy": False, "is_admin": False, "from_search": False, "from_filter": False}
resident = await residents_repository.get_resident_by_id(resident_id)
if not resident:
- return {"info_content": "Ошибка: резидент не найден", "is_busy": False, "from_search": False, "from_filter": False}
+ return {"info_content": "Ошибка: резидент не найден", "is_busy": False, "is_admin": False, "from_search": False, "from_filter": False}
room = await rooms_repository.get_room_by_id(resident.room)
room_number = room.number if room else "???"
@@ -92,6 +101,7 @@ async def get_resident_info_data(
name = resident.real_name if resident.real_name else "Без имени"
status = "🟢 Занят" if resident.is_busy else "⚪️ Свободен"
+ is_admin = False
user_info = "Не привязан"
if resident.user_entity:
user = await users_repository.get_user_by_id(resident.user_entity)
@@ -101,19 +111,28 @@ async def get_resident_info_data(
else:
username = f"ID: {user.id}"
user_info = f"{user.first_name} ({username})"
+ is_admin = user.is_admin
+
+ admin_badge = " 👑" if is_admin else ""
+
+ hours_info = ""
+ if not is_admin:
+ hours_info = f"""
+🟢 Отработанные часы: {resident.inactive_hours} ч
+🔴 Неотработанные часы: {resident.active_hours} ч
+"""
+ else:
+ hours_info = "\n
👑 Это администратор" info_content = f""" -
👤 Информация о резиденте+
👤 Информация о резиденте{admin_badge}ID:
{resident.id}
Имя: {name}
Комната: {room_number}
Статус: {status}
Пользователь: {user_info}
-
-🟢 Отработанные часы: {resident.inactive_hours} ч
-🔴 Неотработанные часы: {resident.active_hours} ч
-"""
+{hours_info}"""
from_search = dialog_manager.dialog_data.get("from_search", False)
from_filter = dialog_manager.dialog_data.get("from_filter", False)
@@ -121,6 +140,7 @@ async def get_resident_info_data(
return {
"info_content": info_content,
"is_busy": resident.is_busy,
+ "is_admin": is_admin,
"from_search": from_search,
"from_filter": from_filter,
}
@@ -478,17 +498,25 @@ async def get_search_results_data(
else:
floor_number = 999999
room_number = 999999
- residents_with_rooms.append((resident, floor_number, room_number))
+
+ is_admin = False
+ if resident.user_entity:
+ user = await users_repository.get_user_by_id(resident.user_entity)
+ if user and user.is_admin:
+ is_admin = True
+
+ residents_with_rooms.append((resident, floor_number, room_number, is_admin))
residents_with_rooms.sort(key=lambda x: (x[1], x[2]))
residents_data = []
- for resident, floor_number, room_number in residents_with_rooms:
+ for resident, floor_number, room_number, is_admin in residents_with_rooms:
status = "🟢" if resident.is_busy else "⚪️"
name = resident.real_name if resident.real_name else "Без имени"
+ admin_badge = " 👑" if is_admin else ""
residents_data.append(
- (f"{name} | Комната {room_number} | {status}", resident.id)
+ (f"{name} | Комната {room_number} | {status}{admin_badge}", resident.id)
)
content = f"""
@@ -565,28 +593,32 @@ resident_info_window = Window(
Const("Добавить часы"),
id="add_hours_btn",
on_click=lambda c, b, m: m.switch_to(AdminMenuSG.add_hours_select),
+ when=~F["is_admin"],
),
Button(
Const("Отнять часы"),
id="remove_hours_btn",
on_click=lambda c, b, m: m.switch_to(AdminMenuSG.remove_hours_select),
+ when=~F["is_admin"],
),
),
Button(
Const("🔄 Перепривязать к комнате"),
id="rebind_resident_btn",
on_click=on_rebind_resident,
+ when=~F["is_admin"],
),
Button(
Const("🚪 Разлогинить"),
id="logout_resident_btn",
on_click=lambda c, b, m: m.switch_to(AdminMenuSG.resident_logout_confirm),
- when="is_busy",
+ when=F["is_busy"] & ~F["is_admin"],
),
Button(
Const("🗑 Удалить резидента"),
id="delete_resident_btn",
on_click=lambda c, b, m: m.switch_to(AdminMenuSG.resident_delete_confirm),
+ when=~F["is_admin"],
),
SwitchTo(
Const("◀️ Назад к результатам поиска"),