Загрузка данных


Работаем с запуском функционала окна Создание файлов для экспертов. Я даюто что должно быть сделано.
1. Пользователь выбирает из выпадающего списка группу
2. Нажмиает Добавить в список.
3. В поле Количество экспертов печатает количество. Сейчас почему то там постоянно стоит единица - ее надо убрать. 
4. В поле Выбранные группы попадают выбранные в выпадающем списке группы. При желании или ошибкепользователь выбирает уже выбраннуюгруппу и кнопкой Удалить выбранное удаляет ее.
5. При нажатии Экспортировать в Эксель все группы создают эксель файл,где одна группа это один лист. Из показателей группы на листе создается матрица. Ее структура следующая:
Начиная с А1 с самого угла в первой строке идет шапка:
№п/п\ Наименование показателя\Код\
Далее таблица автоматически заполняется.  В верху идет код показателей. С ячейки Д2 ставится автоматически единица по лиагонали вниз и вправо. 
№п/п - понятно
Название показателя - полное название показателя из базы данных
Код - код показателя из базы данных.

Ниже высылаю код. Проверь так или не так здесь все это организовано. Если так то хорошо,если нет - то поправь. Созданный файл должен записываться по адресу dukora/excel/ 
Заголовок файла эксель должен состоять из слова Эксперт,подчеркивания и цифры 1,2,3 ....по количеству экспертов.

Не забывай,я вайбер. 


# ============================================
# dukora/modules/experts/create_tab_inter.py
#=============================================
import customtkinter as ctk
from modules.experts.create_tab_work import CreateWorkController


class CreateExpertTab(ctk.CTkFrame):
    def __init__(self, master):
        super().__init__(master)
        self.controller = CreateWorkController()
        
        # Основной контейнер
        main_container = ctk.CTkFrame(self, fg_color="transparent")
        main_container.pack(fill="both", expand=True, padx=10, pady=10)
        
        # --- СЕТКА ИНТЕРФЕЙСА ---
        # Колонка 0: Заголовки (фиксированная ширина, выравнивание по левому краю)
        main_container.grid_columnconfigure(0, weight=0, minsize=180) 
        # Колонка 1: Поля ввода (растягиваются)
        main_container.grid_columnconfigure(1, weight=1)             
        # Колонка 2: Кнопка "Добавить"
        main_container.grid_columnconfigure(2, weight=0, minsize=140) 
        
        # Строка 1: Выбор группы
        lbl_group = ctk.CTkLabel(main_container, text="Выбрать группу:", anchor="w") # Текст внутри метки слева
        lbl_group.grid(row=0, column=0, sticky="w", padx=(0, 5), pady=5)          # Сама метка прижата к левому краю колонки
        
        self.group_cb = ctk.CTkComboBox(main_container, state="readonly", width=200)
        self.group_cb.grid(row=0, column=1, sticky="ew", padx=5, pady=5)
        
        btn_add_group = ctk.CTkButton(main_container, text="Добавить в список", command=self.add_to_list)
        btn_add_group.grid(row=0, column=2, sticky="ew", padx=5, pady=5)
        
        # Строка 2: Количество экспертов
        lbl_count = ctk.CTkLabel(main_container, text="Количество экспертов:", anchor="w")
        lbl_count.grid(row=1, column=0, sticky="w", padx=(0, 5), pady=5)
        
        self.num_experts_entry = ctk.CTkEntry(main_container, justify="center", width=80)
        self.num_experts_entry.insert(0, "1")
        self.num_experts_entry.grid(row=1, column=1, sticky="ew", padx=5, pady=5)
        
        # Строка 3+ : Выбранные группы (Текстовое поле занимает все 3 колонки)
        list_lbl = ctk.CTkLabel(main_container, text="Выбранные группы:")
        list_lbl.grid(row=2, column=0, columnspan=3, sticky="w", pady=(15, 2))
        
        self.selected_text = ctk.CTkTextbox(main_container, height=120, width=540)
        self.selected_text.grid(row=3, column=0, columnspan=3, sticky="ew", pady=(0, 15))
        
        # --- НИЖНЯЯ ПАНЕЛЬ КНОПОК ---
        btn_frame = ctk.CTkFrame(self, fg_color="transparent")
        btn_frame.pack(fill="x", side="bottom", pady=10, padx=10)
        
        del_btn = ctk.CTkButton(btn_frame, text="Удалить выбранное", fg_color="#d9534f", hover_color="#c9302c", command=self.remove_last, width=160)
        del_btn.pack(side="left", expand=True, fill="x", padx=5)
        
        export_btn = ctk.CTkButton(btn_frame, text="Экспортировать в Excel", command=self.export_all, width=180)
        export_btn.pack(side="left", expand=True, fill="x", padx=5)
        
        self.refresh_groups()

    def refresh_groups(self):
        self.controller.refresh_groups()
        self.group_cb.configure(values=self.controller.groups)
        if self.controller.groups:
            self.group_cb.set(self.controller.groups[0])
            
    def add_to_list(self):
        group = self.group_cb.get().strip()
        num_str = self.num_experts_entry.get().strip()
        
        if not group or not num_str.isdigit():
            ctk.CTkMessagebox.showwarning("Ошибка ввода", "Проверьте Группу и Количество.")
            return
            
        line = f"{group} (x{num_str})"
        current = self.selected_text.get("1.0", "end").strip()
        
        lines = [l.strip() for l in current.split('\n') if l.strip()]
        if line not in lines:
            self.selected_text.insert("end", line + "\n")
            self.num_experts_entry.delete(0, "end")
            self.num_experts_entry.insert(0, "1")
            
    def remove_last(self):
        content = self.selected_text.get("1.0", "end").strip().split("\n")
        if not content or content == ['']:
            return
        content.pop(-1)
        self.selected_text.delete("1.0", "end")
        if content:
            self.selected_text.insert("end", "\n".join(content) + "\n")
            
    def export_all(self):
        raw = self.selected_text.get("1.0", "end").strip()
        if not raw:
            ctk.CTkMessagebox.showwarning("Внимание", "Список групп пуст.", type="warning")
            return
            
        parsed = []
        for line in raw.split("\n"):
            if "(x" in line and ")" in line:
                name = line[:line.rfind(" (x")]
                count_str = line[line.rfind("x")+1:-1]
                try:
                    count = int(count_str)
                    parsed.append({"name": name.strip(), "count": count})
                except ValueError:
                    continue
                    
        success = self.controller.export_batch(parsed)
        if success:
            total = sum(x["count"] for x in parsed)
            ctk.CTkMessagebox.showinfo("Готово", f"Создано {total} экспертных файлов.")
            










# =========================================
# dukora/modules/experts/create_tab_work.py
# =========================================
import os
from pathlib import Path
from openpyxl import Workbook
from modules.data.database_module import get_db_connection

def create_excel_template(groups_objects, output_filename):
    wb = Workbook()
    if "Sheet" in wb.sheetnames:
        del wb["Sheet"]
        
    created_any_sheet = False
    
    for group_data in groups_objects:
        indicators_list = group_data.get('indicators', [])
        if not indicators_list:
            continue
            
        sheet_title = f"{group_data['name']}"[:31]
        ws = wb.create_sheet(title=sheet_title)
        created_any_sheet = True
        
        headers = ["№ п/п", "Наименование показателя", "Код"]
        for col_idx, header in enumerate(headers, start=1):
            ws.cell(row=1, column=col_idx, value=header)
            
        for idx, ind_tuple in enumerate(indicators_list, start=1):
            name, code = ind_tuple[0], ind_tuple[1]
            ws.cell(row=idx + 1, column=1, value=str(idx))
            ws.cell(row=idx + 1, column=2, value=name)
            ws.cell(row=idx + 1, column=3, value=code)
            
        matrix_start_col = 4
        matrix_start_row = 1
        
        for col_offset, ind_tuple in enumerate(indicators_list):
            code = ind_tuple[1]
            ws.cell(row=matrix_start_row, column=matrix_start_col + col_offset, value=code)
            
        size = len(indicators_list)
        for r in range(size):
            for c in range(size):
                cell = ws.cell(row=matrix_start_row + r + 1, column=matrix_start_col + c)
                cell.value = 1.0 if r == c else ""
                
    if not created_any_sheet:
        ws = wb.create_sheet(title="Пусто")
        ws['A1'] = 'Нет индикаторов.'
        
    project_root = Path(__file__).resolve().parents[3]
    experts_folder = project_root / "experts"
    os.makedirs(experts_folder, exist_ok=True)
    full_path = experts_folder / output_filename
    wb.save(full_path)

class CreateWorkController:
    def __init__(self):
        self.groups = []
        self._gid_cache = {}
        
    def refresh_groups(self):
        conn = get_db_connection()
        if not conn:
            self.groups = ["Ошибка БД"]
            return
            
        cursor = conn.cursor()
        cursor.execute("SELECT id, name FROM groups ORDER BY name")
        rows = cursor.fetchall()
        
        self.groups = [row[1] for row in rows]
        self._gid_cache = {row[1]: row[0] for row in rows}
        
        conn.close()
        
    def export_batch(self, selected_groups_with_counts):
        if not selected_groups_with_counts:
            return False
            
        conn = get_db_connection()
        if not conn:
            return False
            
        cursor = conn.cursor()
        enriched = []
        for g in selected_groups_with_counts:
            gid = self._gid_cache.get(g["name"])
            if not gid:
                continue
                
            cursor.execute("""
                SELECT name, code 
                FROM indicators 
                WHERE group_code IN (
                    SELECT code FROM groups WHERE id = ?
                ) 
                ORDER BY name
            """, (gid,))
            
            indicators = cursor.fetchall()
            enriched.append({'name': g["name"], 'indicators': indicators})
            
        conn.close()
        if not enriched:
            return False
            
        base_success = True
        expert_counter = 1
        
        for item in selected_groups_with_counts:
            for i in range(item["count"]):
                filename = f"Эксперт_{expert_counter}.xlsx"
                create_excel_template(enriched, filename)
                expert_counter += 1
                
        return base_success
        









# ================================================
# dukora/modules/data/database_module.py
# ================================================
import sys
import sqlite3
from pathlib import Path

try:
    # Определяем корень проекта надежно:
    # Если мы находимся внутри подпапок dukora, идем вверх до корня репозитория.
    # __file__ дает путь к текущему .py файлу.
    current_file_path = Path(__file__).resolve()
    
    # Ищем папку 'dukora' в родителях
    try:
        dukora_parent = current_file_path.parents[[p.name for p in current_file_path.parents].index('dukora')]
    except ValueError:
        # Если по какой-то причине не нашли (запуск из другой директории), берем CWD
        dukora_parent = Path.cwd()
        
    BASE_DIR = dukora_parent
    
    DB_FOLDER = BASE_DIR / "db"
    ACTIVE_FLAG_PATH = DB_FOLDER / "active.flag"
    
    DEFAULT_DB_PATH = DB_FOLDER / "database.db"
    MASTERDATA_PATH = DB_FOLDER / "masterdata.db"
except Exception as e:
    print(f"[FATAL] Не удалось вычислить путь к базе данных: {e}")
    sys.exit(1)

def _determine_active_path():
    try:
        if ACTIVE_FLAG_PATH.exists():
            with open(ACTIVE_FLAG_PATH, 'r', encoding='utf-8') as f:
                name = f.read().strip()
                if name == "masterdata":
                    return MASTERDATA_PATH
    except Exception:
        pass
    return DEFAULT_DB_PATH

DB_PATH = _determine_active_path()

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session as SqlAlchemySession, declarative_base

engine = create_engine(f"sqlite:///{DB_PATH}", echo=False, connect_args={"check_same_thread": False})
Base = declarative_base()
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def get_db():
    db_session = SessionLocal()
    try:
        yield db_session
    finally:
        db_session.close()

def get_db_connection():
    active_path = _determine_active_path()
    global DB_PATH
    DB_PATH = active_path
    
    # === КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ ===
    # Сначала проверяем наличие папки и создаем её ДО попытки открыть БД
    if not DB_FOLDER.exists():
        print(f"[ACTION] Создаю папку для базы: {DB_FOLDER}")
        try:
            DB_FOLDER.mkdir(parents=True, exist_ok=True)
        except PermissionError as pe:
            print(f"[FATAL] Нет прав на создание папки '{DB_FOLDER}'. Запустите терминал от имени Администратора.")
            raise SystemExit(1)
            
    conn = sqlite3.connect(active_path)
    conn.execute("PRAGMA foreign_keys = ON;")
    return conn

def _create_tables(cursor):
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS groups (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL UNIQUE,
    code TEXT NOT NULL UNIQUE,
    gain_group REAL DEFAULT 0.0
    )
    ''')
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS indicators (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL UNIQUE,
    code TEXT NOT NULL UNIQUE,
    group_code TEXT NOT NULL,
    FOREIGN KEY (group_code) REFERENCES groups(code) ON DELETE CASCADE
    )
    ''')

def _populate_test_data(conn):
    cursor = conn.cursor()
    cursor.execute("SELECT COUNT(*) FROM groups")
    count = cursor.fetchone()[0]
    if count == 0:
        print("[INFO] База данных пуста. Заполняю тестовыми данными...")
        groups_data = [("Клубы", "KB", 1.5), ("Фанаты", "FAN", 2.0)]
        cursor.executemany("INSERT INTO groups (name, code, gain_group) VALUES (?, ?, ?)", groups_data)
        indicators_data = [
            ("ЦСКА", "KB-1", "KB"), ("Спартак", "KB-2", "KB"), ("Зенит", "KB-3", "KB"),
            ("Мужчины", "FAN-1", "FAN"), ("Женщины", "FAN-2", "FAN"), ("Дети", "FAN-3", "FAN")
        ]
        cursor.executemany("INSERT INTO indicators (name, code, group_code) VALUES (?, ?, ?)", indicators_data)
        conn.commit()
        print("[SUCCESS] Тестовые данные успешно записаны.")
    else:
        print("[INFO] База данных уже содержит данные. Пропускаю инициализацию.")

def init_database():
    print("[INIT] Проверка наличия рабочих баз данных...")
    conn = None
    try:
        conn = get_db_connection()
        cursor = conn.cursor()
        _create_tables(cursor)
        _populate_test_data(conn)
    except sqlite3.Error as e:
        print(f"[ERROR] Ошибка при инициализации DATABASE.DB: {e}")
        if conn:
            conn.rollback()
    finally:
        if conn:
            conn.close()
            
    master_conn = None
    try:
        if not MASTERDATA_PATH.exists():
            print(f"[ACTION] База мастер-данных не найдена. Создаю новую: {MASTERDATA_PATH.name}")
            master_conn = sqlite3.connect(MASTERDATA_PATH)
            master_conn.execute("PRAGMA foreign_keys = ON;")
            master_cursor = master_conn.cursor()
            _create_tables(master_cursor)
            _populate_test_data(master_conn)
    except sqlite3.Error as e:
        print(f"[ERROR] Ошибка при инициализации MASTERDATA.DB: {e}")
        if master_conn:
            master_conn.rollback()
    finally:
        if master_conn:
            master_conn.close()

class Session:
    def __init__(self):
        pass

# --- ФУНКЦИИ ДЛЯ ИМПОРТА (лежат строго вне класса Session) ---
def list_groups():
    """Функция-заглушка для прямого импорта"""
    return [(1, "Клубы"), (2, "Фанаты")]

def get_all_indicators_by_group(group_code):
    """Функция-заглушка для прямого импорта"""
    mock_data = {
        "KB": [(1, "ЦСКА"), (2, "Спартак"), (3, "Зенит")],
        "FAN": [(4, "Мужчины"), (5, "Женщины"), (6, "Дети")]
    }
    return mock_data.get(group_code, [])

if __name__ == "__main__":
    init_database()