mirror of
https://github.com/koloideal/DutyLog.git
synced 2026-06-10 10:25:29 +03:00
update
This commit is contained in:
+251
@@ -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())
|
||||||
@@ -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())
|
||||||
@@ -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 $$;
|
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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())
|
||||||
@@ -27,6 +27,7 @@ async def get_residents_list_data(
|
|||||||
residents_repository: FromDishka[ResidentsRepository],
|
residents_repository: FromDishka[ResidentsRepository],
|
||||||
rooms_repository: FromDishka[RoomsRepository],
|
rooms_repository: FromDishka[RoomsRepository],
|
||||||
floors_repository: FromDishka[FloorsRepository],
|
floors_repository: FromDishka[FloorsRepository],
|
||||||
|
users_repository: FromDishka[UsersRepository],
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
all_residents = await residents_repository.get_all_residents()
|
all_residents = await residents_repository.get_all_residents()
|
||||||
@@ -41,17 +42,25 @@ async def get_residents_list_data(
|
|||||||
else:
|
else:
|
||||||
floor_number = 999999
|
floor_number = 999999
|
||||||
room_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_with_rooms.sort(key=lambda x: (x[1], x[2]))
|
||||||
|
|
||||||
residents_data = []
|
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 "⚪️"
|
status = "🟢" if resident.is_busy else "⚪️"
|
||||||
name = resident.real_name if resident.real_name else "Без имени"
|
name = resident.real_name if resident.real_name else "Без имени"
|
||||||
|
admin_badge = " 👑" if is_admin else ""
|
||||||
|
|
||||||
residents_data.append(
|
residents_data.append(
|
||||||
(f"{name} | Комната {room_number} | {status}", resident.id)
|
(f"{name} | Комната {room_number} | {status}{admin_badge}", resident.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
content = f"""
|
content = f"""
|
||||||
@@ -79,12 +88,12 @@ async def get_resident_info_data(
|
|||||||
resident_id = dialog_manager.dialog_data.get("selected_resident_id")
|
resident_id = dialog_manager.dialog_data.get("selected_resident_id")
|
||||||
|
|
||||||
if not 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)
|
resident = await residents_repository.get_resident_by_id(resident_id)
|
||||||
|
|
||||||
if not resident:
|
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 = await rooms_repository.get_room_by_id(resident.room)
|
||||||
room_number = room.number if room else "???"
|
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 "Без имени"
|
name = resident.real_name if resident.real_name else "Без имени"
|
||||||
status = "🟢 Занят" if resident.is_busy else "⚪️ Свободен"
|
status = "🟢 Занят" if resident.is_busy else "⚪️ Свободен"
|
||||||
|
|
||||||
|
is_admin = False
|
||||||
user_info = "Не привязан"
|
user_info = "Не привязан"
|
||||||
if resident.user_entity:
|
if resident.user_entity:
|
||||||
user = await users_repository.get_user_by_id(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:
|
else:
|
||||||
username = f"ID: {user.id}"
|
username = f"ID: {user.id}"
|
||||||
user_info = f"{user.first_name} ({username})"
|
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"""
|
||||||
|
🟢 <b>Отработанные часы:</b> <code>{resident.inactive_hours}</code> ч
|
||||||
|
🔴 <b>Неотработанные часы:</b> <code>{resident.active_hours}</code> ч
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
hours_info = "\n<blockquote>👑 <b>Это администратор</b></blockquote>"
|
||||||
|
|
||||||
info_content = f"""
|
info_content = f"""
|
||||||
<blockquote>👤 <b>Информация о резиденте</b></blockquote>
|
<blockquote>👤 <b>Информация о резиденте{admin_badge}</b></blockquote>
|
||||||
|
|
||||||
<b>ID:</b> <code>{resident.id}</code>
|
<b>ID:</b> <code>{resident.id}</code>
|
||||||
<b>Имя:</b> {name}
|
<b>Имя:</b> {name}
|
||||||
<b>Комната:</b> <code>{room_number}</code>
|
<b>Комната:</b> <code>{room_number}</code>
|
||||||
<b>Статус:</b> {status}
|
<b>Статус:</b> {status}
|
||||||
<b>Пользователь:</b> {user_info}
|
<b>Пользователь:</b> {user_info}
|
||||||
|
{hours_info}"""
|
||||||
🟢 <b>Отработанные часы:</b> <code>{resident.inactive_hours}</code> ч
|
|
||||||
🔴 <b>Неотработанные часы:</b> <code>{resident.active_hours}</code> ч
|
|
||||||
"""
|
|
||||||
|
|
||||||
from_search = dialog_manager.dialog_data.get("from_search", False)
|
from_search = dialog_manager.dialog_data.get("from_search", False)
|
||||||
from_filter = dialog_manager.dialog_data.get("from_filter", False)
|
from_filter = dialog_manager.dialog_data.get("from_filter", False)
|
||||||
@@ -121,6 +140,7 @@ async def get_resident_info_data(
|
|||||||
return {
|
return {
|
||||||
"info_content": info_content,
|
"info_content": info_content,
|
||||||
"is_busy": resident.is_busy,
|
"is_busy": resident.is_busy,
|
||||||
|
"is_admin": is_admin,
|
||||||
"from_search": from_search,
|
"from_search": from_search,
|
||||||
"from_filter": from_filter,
|
"from_filter": from_filter,
|
||||||
}
|
}
|
||||||
@@ -478,17 +498,25 @@ async def get_search_results_data(
|
|||||||
else:
|
else:
|
||||||
floor_number = 999999
|
floor_number = 999999
|
||||||
room_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_with_rooms.sort(key=lambda x: (x[1], x[2]))
|
||||||
|
|
||||||
residents_data = []
|
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 "⚪️"
|
status = "🟢" if resident.is_busy else "⚪️"
|
||||||
name = resident.real_name if resident.real_name else "Без имени"
|
name = resident.real_name if resident.real_name else "Без имени"
|
||||||
|
admin_badge = " 👑" if is_admin else ""
|
||||||
|
|
||||||
residents_data.append(
|
residents_data.append(
|
||||||
(f"{name} | Комната {room_number} | {status}", resident.id)
|
(f"{name} | Комната {room_number} | {status}{admin_badge}", resident.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
content = f"""
|
content = f"""
|
||||||
@@ -565,28 +593,32 @@ resident_info_window = Window(
|
|||||||
Const("Добавить часы"),
|
Const("Добавить часы"),
|
||||||
id="add_hours_btn",
|
id="add_hours_btn",
|
||||||
on_click=lambda c, b, m: m.switch_to(AdminMenuSG.add_hours_select),
|
on_click=lambda c, b, m: m.switch_to(AdminMenuSG.add_hours_select),
|
||||||
|
when=~F["is_admin"],
|
||||||
),
|
),
|
||||||
Button(
|
Button(
|
||||||
Const("Отнять часы"),
|
Const("Отнять часы"),
|
||||||
id="remove_hours_btn",
|
id="remove_hours_btn",
|
||||||
on_click=lambda c, b, m: m.switch_to(AdminMenuSG.remove_hours_select),
|
on_click=lambda c, b, m: m.switch_to(AdminMenuSG.remove_hours_select),
|
||||||
|
when=~F["is_admin"],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Button(
|
Button(
|
||||||
Const("🔄 Перепривязать к комнате"),
|
Const("🔄 Перепривязать к комнате"),
|
||||||
id="rebind_resident_btn",
|
id="rebind_resident_btn",
|
||||||
on_click=on_rebind_resident,
|
on_click=on_rebind_resident,
|
||||||
|
when=~F["is_admin"],
|
||||||
),
|
),
|
||||||
Button(
|
Button(
|
||||||
Const("🚪 Разлогинить"),
|
Const("🚪 Разлогинить"),
|
||||||
id="logout_resident_btn",
|
id="logout_resident_btn",
|
||||||
on_click=lambda c, b, m: m.switch_to(AdminMenuSG.resident_logout_confirm),
|
on_click=lambda c, b, m: m.switch_to(AdminMenuSG.resident_logout_confirm),
|
||||||
when="is_busy",
|
when=F["is_busy"] & ~F["is_admin"],
|
||||||
),
|
),
|
||||||
Button(
|
Button(
|
||||||
Const("🗑 Удалить резидента"),
|
Const("🗑 Удалить резидента"),
|
||||||
id="delete_resident_btn",
|
id="delete_resident_btn",
|
||||||
on_click=lambda c, b, m: m.switch_to(AdminMenuSG.resident_delete_confirm),
|
on_click=lambda c, b, m: m.switch_to(AdminMenuSG.resident_delete_confirm),
|
||||||
|
when=~F["is_admin"],
|
||||||
),
|
),
|
||||||
SwitchTo(
|
SwitchTo(
|
||||||
Const("◀️ Назад к результатам поиска"),
|
Const("◀️ Назад к результатам поиска"),
|
||||||
|
|||||||
Reference in New Issue
Block a user