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



import json
import os

import classes


FILE_NAME = os.path.join(
    os.path.dirname(os.path.abspath(__file__)),
    "data.json"
)


def user_to_dict(user):
    return {
        "id": user.id,
        "name": user.name,
        "email": user.email,
        "grade": user.grade,
        "points": user.points
    }


def task_to_dict(task):
    data = {
        "id": task.id,
        "title": task.title,
        "priority": task.priority,
        "status": task.status,
        "worker_id": task.worker.id if task.worker is not None else None,
        "reviewer_id": task.reviewer.id if task.reviewer is not None else None,
        "returns": task._returns,
        "dependency_id": (
            task._be_need_by.id if task._be_need_by is not None else None
        )
    }

    if isinstance(task, classes.FeatureTask):
        data["type"] = "feature"
        data["hours"] = task.hours

    elif isinstance(task, classes.BugTask):
        data["type"] = "bug"
        data["error"] = task.error

    elif isinstance(task, classes.PassiveTask):
        data["type"] = "passive"

    else:
        raise ValueError("Неизвестный тип задачи")

    return data


def save_data(users, tasks, next_id):
    data = {
        "next_id": next_id,
        "users": [],
        "tasks": []
    }

    for user in users:
        data["users"].append(user_to_dict(user))

    for task in tasks:
        data["tasks"].append(task_to_dict(task))

    temp_file = FILE_NAME + ".tmp"

    with open(temp_file, "w", encoding="utf-8") as file:
        json.dump(data, file, ensure_ascii=False, indent=4)
        file.flush()
        os.fsync(file.fileno())

    os.replace(temp_file, FILE_NAME)


def load_data():
    if not os.path.exists(FILE_NAME):
        return [], [], 1

    try:
        with open(FILE_NAME, "r", encoding="utf-8") as file:
            data = json.load(file)

        if not isinstance(data, dict):
            raise ValueError("Неверный формат файла")

        if set(data.keys()) != {"next_id", "users", "tasks"}:
            raise ValueError("Неверные ключи файла")

        next_id = data["next_id"]
        user_records = data["users"]
        task_records = data["tasks"]

        if type(next_id) is not int or next_id < 1:
            raise ValueError("Неверный счётчик ID")

        if not isinstance(user_records, list):
            raise ValueError("Неверный список пользователей")

        if not isinstance(task_records, list):
            raise ValueError("Неверный список задач")

        users = []
        tasks = []

        user_by_id = {}
        task_by_id = {}

        emails = set()
        all_ids = set()

        # Восстанавливаем пользователей
        for record in user_records:
            user_id = record["id"]

            if type(user_id) is not int or user_id < 1:
                raise ValueError("Неверный ID пользователя")

            if user_id in all_ids:
                raise ValueError("Повторяющийся ID")

            user = classes.Member(
                user_id,
                record["name"],
                record["email"]
            )

            if user.email in emails:
                raise ValueError("Повторяющийся email")

            points = record["points"]
            grade = record["grade"]

            if type(points) is not int or points < 0:
                raise ValueError("Неверное количество баллов")

            expected_grade = "junior"

            if points >= 150:
                expected_grade = "senior"
            elif points >= 50:
                expected_grade = "middle"

            if grade != expected_grade:
                raise ValueError("Уровень не соответствует баллам")

            user._points = points
            user._grade = grade

            users.append(user)
            user_by_id[user_id] = user

            emails.add(user.email)
            all_ids.add(user_id)

        # Восстанавливаем задачи
        for record in task_records:
            task_id = record["id"]
            task_type = record["type"]

            if type(task_id) is not int or task_id < 1:
                raise ValueError("Неверный ID задачи")

            if task_id in all_ids:
                raise ValueError("Повторяющийся ID")

            if task_type == "feature":
                task = classes.FeatureTask(
                    task_id,
                    record["title"],
                    record["priority"],
                    hours=record["hours"]
                )

            elif task_type == "bug":
                task = classes.BugTask(
                    task_id,
                    record["title"],
                    record["priority"],
                    error=record["error"]
                )

            elif task_type == "passive":
                task = classes.PassiveTask(
                    task_id,
                    record["title"],
                    record["priority"]
                )

            else:
                raise ValueError("Неизвестный тип задачи")

            tasks.append(task)
            task_by_id[task_id] = task
            all_ids.add(task_id)

        if all_ids and next_id <= max(all_ids):
            raise ValueError("Неверное состояние счётчика ID")

        # Восстанавливаем связи и состояния
        for record, task in zip(task_records, tasks):
            worker_id = record["worker_id"]
            reviewer_id = record["reviewer_id"]
            dependency_id = record["dependency_id"]

            if worker_id is not None:
                task._worker = user_by_id[worker_id]

            if reviewer_id is not None:
                task._reviewer = user_by_id[reviewer_id]

            if dependency_id is not None:
                task._be_need_by = task_by_id[dependency_id]

            task._status = record["status"]
            task._returns = record["returns"]

        valid_statuses = {
            "новая",
            "в работе",
            "на проверке",
            "готова",
            "отменена"
        }

        # Проверяем восстановленные данные
        for task in tasks:
            if task.status not in valid_statuses:
                raise ValueError("Неизвестный статус задачи")

            if type(task._returns) is not int or task._returns < 0:
                raise ValueError("Неверное количество возвратов")

            if task.worker is not None and task.worker is task.reviewer:
                raise ValueError("Исполнитель совпадает с проверяющим")

            visited = set()
            current = task

            while current is not None:
                if current.id in visited:
                    raise ValueError("Обнаружен цикл зависимостей")

                visited.add(current.id)
                current = current._be_need_by

        # Проверяем лимиты активных задач
        for user in users:
            active_count = 0

            for task in tasks:
                if task.worker is user and task.status in (
                    "в работе",
                    "на проверке"
                ):
                    active_count += 1

            limits = {
                "junior": 1,
                "middle": 2,
                "senior": 3
            }

            if active_count > limits[user.grade]:
                raise ValueError("Превышен лимит нагрузки")

        return users, tasks, next_id

    except (
        OSError,
        ValueError,
        TypeError,
        KeyError,
        AttributeError,
        json.JSONDecodeError
    ) as error:
        raise ValueError(
            f"Ошибка загрузки данных: {error}"
        ) from error