megaplan-sdk 0.2.1
Professional Python SDK for Megaplan API v3
Requires Python: >=3.11
Keywords: megaplan, crm, api, sdk

# Megaplan Python SDK

[![Python versions](https://img.shields.io/pypi/pyversions/megaplan-sdk)](https://pypi.org/project/megaplan-sdk/)
[![PyPI version](https://badge.fury.io/py/megaplan-sdk.svg)](https://pypi.org/project/megaplan-sdk/)
[![Tests](https://github.com/borzov/megaplan-sdk/actions/workflows/tests.yml/badge.svg)](https://github.com/borzov/megaplan-sdk/actions/workflows/tests.yml)
[![Coverage](https://codecov.io/gh/borzov/megaplan-sdk/graph/badge.svg)](https://codecov.io/gh/borzov/megaplan-sdk)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/charliermarsh/ruff)

Профессиональная Python-библиотека для работы с API Мегаплана версии 3.

## О проекте

Современная библиотека для интеграции с CRM Мегаплан. Она предоставляет удобный и типобезопасный интерфейс для работы с задачами, проектами, сделками и другими сущностями через REST API.

### Зачем нужна эта библиотека?

Работа с API Мегаплана напрямую требует знания множества технических деталей: правильной настройки OAuth2-авторизации, обработки токенов, формирования JSON-параметров в query string, обработки ошибок и пагинации. Эта библиотека берет на себя всю рутинную работу, позволяя разработчикам сосредоточиться на бизнес-логике.

### Преимущества использования SDK

Вместо прямых HTTP-запросов вы получаете простой и понятный Python-интерфейс. Вместо ручной работы с токенами — автоматическую авторизацию и обновление токенов. Вместо парсинга JSON-ответов — типизированные Pydantic-модели с автодополнением в IDE. Вместо обработки ошибок вручную — понятные исключения с детальной информацией.

Библиотека полностью асинхронная, что позволяет эффективно работать с большими объемами данных и выполнять параллельные запросы. Встроенная логика повторных попыток при временных сбоях сервера делает интеграцию более надежной. Модульная архитектура позволяет легко расширять функциональность и добавлять поддержку новых модулей API.

## Содержание

### Основы
- [Быстрый старт](#быстрый-старт)
- [Авторизация](#авторизация)
- [Helper-функции](#helper-функции)
- [Обработка ошибок](#обработка-ошибок)

### Работа с сущностями
- [Общие паттерны](#общие-паттерны-работы-с-сущностями)
- [Задачи](#работа-с-задачами)
- [Проекты](#работа-с-проектами)
- [Сделки](#работа-со-сделками)

### Продвинутые возможности
- [Кэширование сущностей](#кэширование-сущностей)
- [Глобальные дефолтные лимиты](#глобальные-дефолтные-лимиты)
- [Автоматическая подгрузка связанных сущностей](#автоматическая-подгрузка-связанных-сущностей)
- [Работа с фильтрами](#работа-с-фильтрами)
- [Настройка HTTP-клиента](#настройка-http-клиента)
- [Работа через прокси](#работа-через-прокси)
- [Ручное управление токенами](#ручное-управление-токенами)

### Справочная информация
- [Известные ограничения API](#известные-ограничения-api)
- [Архитектура](#архитектура)
- [Требования](#требования)
- [Разработка](#разработка)

## Возможности

- Полный CRUD для задач, проектов и сделок
- Метод `get_full_details()` — получение сущности со всеми связанными данными (комментарии, история, подзадачи и т.д.) за один вызов с параллельной загрузкой
- OAuth2-авторизация с автоматическим обновлением токенов
- Типобезопасность с Pydantic-моделями и полной типизацией
- Асинхронность — поддержка async/await во всех операциях
- Автоматические повторы при ошибках сервера (5xx)
- Кэширование сущностей с LRU и TTL для оптимизации запросов
- FilterBuilder для создания фильтров с fluent API
- Параметр `expand` для автоматической подгрузки связанных сущностей
- Метод `iterate()` для автоматической пагинации больших списков
- Helper-функции для создания BaseEntity объектов
- Глобальные дефолтные лимиты для комментариев и истории
- Модульная архитектура для легкого расширения
- Комплексные тесты с покрытием 80%+

## Установка

```bash
pip install megaplan-sdk
```

Или из исходников:

```bash
git clone https://github.com/borzov/megaplan-sdk.git
cd megaplan-sdk
pip install -e .
```

## Быстрый старт

```python
import asyncio
from megaplan_sdk import MegaplanClient

async def main():
    # Создание клиента с учетными данными
    async with MegaplanClient(
        base_url="https://my.megaplan.ru",
        username="user@example.com",
        password="your_password"
    ) as client:

        # Получение списка задач
        tasks = await client.tasks.list(limit=10)
        for task in tasks:
            print(f"Задача: {task.name}")

        # Получение конкретной задачи
        task = await client.tasks.get(task_id=42)
        print(f"Детали задачи: {task.name}, Статус: {task.status}")

        # Упрощенное создание задачи
        new_task = await client.tasks.create_simple(
            "Новая задача",
            employees_resource=client.employees
        )
        print(f"Создана задача: {new_task.name}")

        # Получение задачи со всеми связанными данными за один вызов
        details = await client.tasks.get_full_details(
            task_id=42,
            include_comments=True,
            include_sub_tasks=True,
            include_responsible_details=True
        )
        print(f"Комментариев: {len(details.comments) if details.comments else 0}")
        print(f"Подзадач: {len(details.sub_tasks) if details.sub_tasks else 0}")

if __name__ == "__main__":
    asyncio.run(main())
```

## Авторизация

SDK поддерживает OAuth2-авторизацию. Вы можете передать учетные данные или использовать предварительно полученный токен доступа:

```python
# С логином и паролем (автоматическая авторизация)
client = MegaplanClient(
    base_url="https://my.megaplan.ru",
    username="user@example.com",
    password="password"
)

# С токеном доступа
client = MegaplanClient(
    base_url="https://my.megaplan.ru",
    access_token="your_access_token"
)
```

## Helper-функции

SDK предоставляет удобные функции для создания BaseEntity объектов:

```python
from megaplan_sdk import (
    make_employee_entity,
    make_project_entity,
    make_task_entity,
    make_deal_entity,
    make_contractor_entity,
)

# Вместо ручного создания {"contentType": "Employee", "id": 123}
employee_ref = make_employee_entity(123)
project_ref = make_project_entity(456)
task_ref = make_task_entity(789)
deal_ref = make_deal_entity(101)
contractor_ref = make_contractor_entity(202)
```

## Обработка ошибок

SDK предоставляет специфичные типы исключений для различных сценариев ошибок:

```python
from megaplan_sdk import (
    AuthenticationError,      # 401 - Ошибка аутентификации
    AuthorizationError,        # 403 - Ошибка авторизации (нет прав)
    NotFoundError,            # 404 - Ресурс не найден
    ValidationError,          # 422 - Ошибка валидации запроса
    RateLimitError,           # 429 - Превышен лимит запросов
    ServerError               # 5xx - Ошибка сервера
)

try:
    task = await client.tasks.get(task_id=999)
except NotFoundError:
    print("Задача не найдена")
except AuthenticationError:
    print("Ошибка аутентификации")
except ValidationError as e:
    print(f"Ошибки валидации: {e.errors}")
    # e.errors содержит список ошибок из API
```

## Общие паттерны работы с сущностями

Большинство сущностей (задачи, проекты, сделки) поддерживают одинаковые операции CRUD и паттерны работы. В этом разделе описаны общие методы, которые применяются ко всем типам сущностей.

### Базовые операции CRUD

Все ресурсы поддерживают стандартные операции:

#### Получение списка (`list`)

```python
# Общий формат для всех ресурсов
entities = await client.{resource}.list(
    limit=None,              # int: Количество элементов на странице
    page_after=None,        # dict: Загрузить страницу, начиная с этой сущности
    page_before=None,       # dict: Загрузить страницу строго до этой сущности
    page_with=None,         # dict: Загрузить страницу с наличием этой сущности
    fields=None,            # any: Набор дополнительных полей
    sort_by=None,           # list[dict]: Массив полей сортировки
    only_requested_fields=None  # bool: Отдавать только перечисленные поля
)
```

**Примеры:**
```python
# Получить все задачи
tasks = await client.tasks.list()

# Получить проекты с лимитом
projects = await client.projects.list(limit=50)

# Получить сделки с пагинацией
deals = await client.deals.list(limit=100, page_after={"contentType": "Deal", "id": 100})
```

#### Получение по ID (`get`)

```python
entity = await client.{resource}.get({resource}_id=42)
# Возвращает: объект сущности со всеми полями
```

**Примеры:**
```python
task = await client.tasks.get(task_id=42)
project = await client.projects.get(project_id=5)
deal = await client.deals.get(deal_id=200)
```

#### Создание (`create`)

```python
entity = await client.{resource}.create({resource}_data={
    "name": "Название",  # Обязательное поле
    # ... другие поля
})
# Возвращает: созданная сущность
```

**Примеры:**
```python
# Простое создание задачи
task = await client.tasks.create({"name": "Новая задача"})

# Создание проекта
project = await client.projects.create({"name": "Новый проект"})

# Создание сделки (требует program)
deal = await client.deals.create({
    "name": "Новая сделка",
    "program": {"contentType": "Program", "id": 10}
})
```

#### Обновление (`update`)

```python
entity = await client.{resource}.update(
    {resource}_id=42,
    {resource}_data={
        "name": "Обновленное название",
        # ... другие поля для обновления
    }
)
# Возвращает: обновленная сущность
```

**Примеры:**
```python
task = await client.tasks.update(task_id=42, task_data={"status": "completed"})
project = await client.projects.update(project_id=5, project_data={"name": "Новое название"})
deal = await client.deals.update(deal_id=200, deal_data={"sum_base": 60000.0})
```

#### Удаление (`delete`)

```python
await client.{resource}.delete({resource}_id=42)
# Возвращает: None
```

**Примеры:**
```python
await client.tasks.delete(task_id=42)
await client.projects.delete(project_id=5)
await client.deals.delete(deal_id=200)
```

### Пагинация

SDK поддерживает несколько способов работы с большими списками:

#### Ручная пагинация

```python
# Пагинация "после" определенной сущности
entities = await client.tasks.list(
    limit=50,
    page_after={"contentType": "Task", "id": 100}
)

# Пагинация "до" определенной сущности
entities = await client.tasks.list(
    limit=50,
    page_before={"contentType": "Task", "id": 200}
)

# Пагинация "с" определенной сущностью
entities = await client.tasks.list(
    limit=50,
    page_with={"contentType": "Task", "id": 150}
)
```

#### Автоматическая пагинация с `iterate()`

Метод `iterate()` автоматически обрабатывает пагинацию и возвращает все элементы:

```python
# Итерация по всем задачам
async for task in client.tasks.iterate(limit=100):
    print(task.name)

# Итерация по всем проектам
async for project in client.projects.iterate(limit=50):
    print(project.name)

# Итерация по всем сделкам
async for deal in client.deals.iterate(limit=200):
    print(deal.name)
```

### Получение полной информации (`get_full_details`)

Метод `get_full_details()` позволяет получить сущность со всеми связанными данными за один вызов. Все запросы выполняются параллельно для максимальной производительности.

**Общий формат:**
```python
details = await client.{resource}.get_full_details(
    {resource}_id=42,
    include_comments=True,          # Загрузить комментарии
    include_history=True,            # Загрузить историю изменений
    comments_limit=50,               # Лимит комментариев (опционально)
    history_limit=100                # Лимит записей истории (опционально)
    # ... другие специфичные параметры для каждого типа
)
```

**Примеры для разных типов:**

```python
# Задача со всеми данными
task_details = await client.tasks.get_full_details(
    task_id=42,
    include_comments=True,
    include_sub_tasks=True,
    include_responsible_details=True
)

# Проект со всеми данными
project_details = await client.projects.get_full_details(
    project_id=5,
    include_deals=True,
    include_issues=True,
    include_comments=True
)

# Сделка со всеми данными
deal_details = await client.deals.get_full_details(
    deal_id=200,
    include_comments=True,
    include_status_history=True,
    include_contractor_details=True
)
```

**Доступ к данным:**
```python
# Основная сущность
print(details.task.name)      # для задач
print(details.project.name)   # для проектов
print(details.deal.name)      # для сделок

# Связанные данные
if details.comments:
    for comment in details.comments:
        print(comment.text)

if details.history:
    print(f"Записей в истории: {len(details.history)}")
```

Подробнее о специфичных параметрах для каждого типа сущностей см. в соответствующих разделах:
- [Задачи](#работа-с-задачами)
- [Проекты](#работа-с-проектами)
- [Сделки](#работа-со-сделками)

## Работа с задачами

> **Примечание:** Базовые операции CRUD (list, get, create, update, delete) и пагинация описаны в разделе [Общие паттерны работы с сущностями](#общие-паттерны-работы-с-сущностями).

### Специфичные параметры для задач

#### Получение списка задач с фильтрацией

Метод `list()` поддерживает дополнительные параметры для задач:

```python
tasks = await client.tasks.list(
    filter=None,              # TaskFilter: ID фильтра (int/str) или FilterBuilder объект
    statuses=None,            # list[str]: Статусы задач для фильтрации
    # ... остальные параметры из общих паттернов
)
```

**Примеры использования фильтров:**

```python
# С фильтром по статусам
tasks = await client.tasks.list(
    statuses=["assigned", "in_progress"],
    limit=50
)

# С фильтром по ID (int или str)
tasks = await client.tasks.list(filter=123)
tasks = await client.tasks.list(filter="incoming")

# С FilterBuilder для текстового поиска (рекомендуется)
from megaplan_sdk import TaskFilterBuilder

# Простой поиск по названию
filter_obj = TaskFilterBuilder().field("name").contains("договор").build()
tasks = await client.tasks.list(filter=filter_obj)

# Несколько условий с AND
filter_obj = (
    TaskFilterBuilder()
    .field("name").contains("договор")
    .and_()
    .field("name").starts_with("Важный")
    .build()
)
tasks = await client.tasks.list(filter=filter_obj)

# Условия с OR
filter_obj = (
    TaskFilterBuilder()
    .field("name").contains("договор")
    .or_()
    .field("name").contains("соглашение")
    .build()
)
tasks = await client.tasks.list(filter=filter_obj)
```

Подробнее о работе с фильтрами см. раздел [Работа с фильтрами](#работа-с-фильтрами).

### Поля модели Task

- `id: int` - Идентификатор задачи
- `name: str` - Название задачи
- `description: str` - Описание
- `status: str` - Статус задачи
- `responsible: BaseEntity` - Ответственный (Employee)
- `owner: BaseEntity` - Владелец (Employee)
- `deadline: str` - Срок выполнения
- `actual_finish: str` - Фактическая дата завершения
- `parent: BaseEntity` - Родительская задача/проект
- `project: BaseEntity` - Проект
- `priority: str` - Приоритет
- `tags: list[BaseEntity]` - Теги
- `attaches: list[BaseEntity]` - Вложения (файлы)
- `todos: list[BaseEntity]` - Подзадачи-чеклисты
- `created_at: str` - Дата создания
- `updated_at: str` - Дата обновления

### Упрощенные методы создания

Помимо стандартного `create()`, задачи поддерживают упрощенные методы:

```python
# Создание задачи с текущим пользователем как ответственным
task = await client.tasks.create_simple(
    "Новая задача",
    employees_resource=client.employees  # Автоматически определит текущего пользователя
)

# Создание задачи с указанным ответственным
task = await client.tasks.create_simple(
    "Новая задача",
    responsible_id=123
)

# Создание задачи внутри проекта (автоматически устанавливает связь)
task = await client.tasks.create_in_project(
    "Задача в проекте",
    project_id=456,
    employees_resource=client.employees
)
```

**Примечание:** Стандартный метод `create()` также поддерживается. При создании задачи автоматически устанавливаются `isUrgent=False` и `isTemplate=False`, если они не указаны явно.

### Получение подзадач

```python
subtasks = await client.tasks.get_sub_tasks(
    task_id=10,                               # int: Идентификатор задачи
    filters=None,                             # list[dict]: Фильтры типов результатов
    limit=None,                               # int: Количество элементов
    page_after=None,                          # dict: Пагинация после
    page_before=None,                         # dict: Пагинация до
    page_with=None,                           # dict: Пагинация с
    fields=None,                              # any: Дополнительные поля
    sort_by=None,                             # list[dict]: Сортировка
    only_requested_fields=None                # bool: Только запрошенные поля
)
# Возвращает: list[Task] - список подзадач

# Получение актуальных подзадач
actual_subtasks = await client.tasks.get_actual_sub_tasks(
    task_id=10,
    # ... те же параметры
)
# Возвращает: list[Task] - список актуальных подзадач
```

### Получение доступных родителей

Методы для получения доступных надзадач и надпроектов (для выбора родителя при создании или перемещении задачи):

```python
# Глобальный поиск доступных родителей для новой задачи
# Возвращает список Task и Project объектов
parents = await client.tasks.get_available_parents(
    is_template=False,                        # bool: Фильтр по шаблонам
    limit=10,                                 # int: Количество элементов
)
for parent in parents:
    print(f"{type(parent).__name__}: {parent.name}")  # "Task: ..." или "Project: ..."

# Доступные родители для существующей задачи
# Исключает саму задачу и её потомков
parents = await client.tasks.get_available_parents_for(
    task_id=123,
    is_template=False,
    limit=10,
)
```

**Примечание:** Методы возвращают смешанный список объектов `Task` и `Project`, так как задача может быть вложена как в другую задачу, так и в проект.

### Получение всех участников задачи

Метод `get_all_participants()` возвращает полный список участников задачи (ответственный, соисполнители, аудиторы, владелец) в одном запросе:

```python
participants = await client.tasks.get_all_participants(
    task_id=123,
    limit=None,                               # int: Количество элементов
    # ... стандартные параметры пагинации
)
# Возвращает: list[Employee | ContractorHuman | Group]

for participant in participants:
    if hasattr(participant, 'display_name'):
        print(participant.display_name())
```

**Типы участников:**
- `Employee` — сотрудник организации
- `ContractorHuman` — контрагент-физлицо
- `Group` — группа участников (например, отдел)

### Получение задач на уровне дерева

```python
tasks = await client.tasks.tree_level(
    filter=None,                              # TaskFilter: Фильтр
    limit=None,                               # int: Количество элементов
    page_after=None,                          # dict: Пагинация
    page_before=None,                         # dict: Пагинация
    page_with=None,                           # dict: Пагинация
    fields=None,                              # any: Дополнительные поля
    sort_by=None,                             # list[dict]: Сортировка
    only_requested_fields=None                # bool: Только запрошенные поля
)
# Возвращает: list[Task | Project] - список задач/проектов текущего уровня
```

### Получение полной информации о задаче

Метод `get_full_details()` для задач поддерживает следующие специфичные параметры:

```python
details = await client.tasks.get_full_details(
    task_id=42,
    include_sub_tasks=True,              # Загрузить подзадачи
    include_actual_sub_tasks=True,       # Загрузить актуальные подзадачи
    include_comments=True,                # Загрузить комментарии
    include_history=True,                 # Загрузить историю изменений
    include_auditors=True,                # Загрузить список аудиторов
    include_executors=True,               # Загрузить соисполнителей
    include_milestones=True,              # Загрузить вехи
    include_responsible_details=True,     # Загрузить полные данные ответственного
    include_owner_details=True,           # Загрузить полные данные постановщика
    comments_limit=50,                    # Лимит комментариев (опционально)
    history_limit=100                    # Лимит записей истории (опционально)
)
```

**Поля объекта TaskFullDetails:**
- `task: Task` - Основная задача
- `sub_tasks: list[Task] | None` - Подзадачи
- `actual_sub_tasks: list[Task] | None` - Актуальные подзадачи
- `comments: list[Comment] | None` - Комментарии
- `history: list[dict] | None` - История изменений
- `auditors: list[dict] | None` - Аудиторы
- `executors: list[dict] | None` - Соисполнители
- `milestones: list[Milestone] | None` - Вехи
- `responsible_details: Employee | None` - Полные данные ответственного
- `owner_details: Employee | None` - Полные данные постановщика

> **Примечание:** Общее описание метода `get_full_details()` и примеры использования см. в разделе [Общие паттерны работы с сущностями](#общие-паттерны-работы-с-сущностями).

### Работа с вехами (Milestones)

Вехи можно получать и создавать для задач и проектов.

#### Получение вех

```python
# Получить вехи задачи
milestones = await client.tasks.get_milestones(
    task_id=123,
    limit=50  # Опционально
)

# Получить вехи проекта
milestones = await client.projects.get_milestones(
    project_id=456,
    limit=50  # Опционально
)

# Вехи также доступны через get_full_details()
details = await client.tasks.get_full_details(
    task_id=123,
    include_milestones=True
)
if details.milestones:
    for milestone in details.milestones:
        print(f"{milestone.name}: {milestone.type}")
```

#### Создание вехи

```python
from megaplan_sdk.models.milestone import Milestone

# Создать веху для задачи
milestone = await client.tasks.add_milestone(
    task_id=123,
    milestone_data={
        "name": "Release 1.0",
        "description": "Release milestone description",  # Обязательное поле
        "type": "report",  # Обязательное: "report", "reminder", или "note"
        "date": "2026-02-01T10:00:00Z"  # Обязательное: ISO 8601 формат
    }
)

# Или использовать модель Milestone
milestone = await client.tasks.add_milestone(
    task_id=123,
    milestone_data=Milestone(
        name="Release 1.0",
        description="Release milestone description",
        type="report",
        date="2026-02-01T10:00:00Z"
    )
)

# Создать веху для проекта
milestone = await client.projects.add_milestone(
    project_id=456,
    milestone_data={
        "description": "Phase 1 completion",
        "type": "reminder",
        "date": "2026-03-15T14:00:00Z"
    }
)
```

**Обязательные поля при создании вехи:**
- `description: str` - Описание вехи
- `type: str` - Тип вехи: `"report"`, `"reminder"`, или `"note"`
- `date: str | DateTime | dict` - Дата и время вехи (ISO 8601 строка или объект DateTime)

**Поля модели Milestone:**
- `id: int` - Идентификатор вехи
- `name: str | None` - Название вехи
- `description: str | None` - Описание
- `completed: bool | None` - Признак завершенности
- `type: str | None` - Тип вехи
- `date: str | DateTime | dict | None` - Дата и время
- `owner: BaseEntity | None` - Создатель (Employee)
- `responsible: BaseEntity | None` - Ответственный (Employee)
- `task: BaseEntity | None` - Связанная задача
- `project: BaseEntity | None` - Связанный проект

**Примечание:** Метод `get_milestones()` может вернуть пустой список для некоторых задач/проектов из-за ограничений API (ошибка 500). Это обрабатывается автоматически.

## Работа с проектами

> **Примечание:** Базовые операции CRUD (list, get, create, update, delete) описаны в разделе [Общие паттерны работы с сущностями](#общие-паттерны-работы-с-сущностями).

**Важно:** Проекты не поддерживают фильтрацию через API (параметр `filter` недоступен).

### Поля модели Project
- `id: int` - Идентификатор проекта
- `name: str` - Название проекта
- `description: str` - Описание
- `status: str` - Статус проекта
- `owner: BaseEntity` - Владелец (Employee)
- `responsible: BaseEntity` - Ответственный (Employee)
- `deadline: str` - Срок выполнения
- `actual_finish: str` - Фактическая дата завершения
- `parent: BaseEntity` - Родительский проект
- `priority: str` - Приоритет
- `tags: list[BaseEntity]` - Теги
- `attaches: list[BaseEntity]` - Вложения
- `todos: list[BaseEntity]` - Подзадачи-чеклисты
- `created_at: str` - Дата создания
- `updated_at: str` - Дата обновления

### Упрощенные методы создания

Помимо стандартного `create()`, проекты поддерживают упрощенный метод:

```python
# Создание проекта с текущим пользователем как владельцем и ответственным
project = await client.projects.create_simple(
    "Новый проект",
    employees_resource=client.employees  # Автоматически определит текущего пользователя
)

# Создание проекта с указанными владельцем и ответственным
project = await client.projects.create_simple(
    "Новый проект",
    owner_id=123,
    responsible_id=123
)
```

**Примечание:** При создании проекта автоматически устанавливается `isTemplate=False`, если не указано явно.

### Получение сделок проекта

```python
deals = await client.projects.get_deals(
    project_id=5,                             # int: Идентификатор проекта
    limit=None,                               # int: Количество элементов
    page_after=None,                          # dict: Пагинация после
    page_before=None,                         # dict: Пагинация до
    page_with=None,                           # dict: Пагинация с
    fields=None,                              # any: Дополнительные поля
    sort_by=None,                             # list[dict]: Сортировка
    only_requested_fields=None                # bool: Только запрошенные поля
)
# Возвращает: list[Deal] - список связанных сделок
```

### Получение задач проекта

```python
issues = await client.projects.get_issues(
    project_id=5,                             # int: Идентификатор проекта
    limit=None,                               # int: Количество элементов
    page_after=None,                          # dict: Пагинация
    page_before=None,                         # dict: Пагинация
    page_with=None,                           # dict: Пагинация
    fields=None,                              # any: Дополнительные поля
    sort_by=None,                             # list[dict]: Сортировка
    only_requested_fields=None                # bool: Только запрошенные поля
)
# Возвращает: list[Task] - список задач проекта

# Получение актуальных задач проекта
actual_issues = await client.projects.get_actual_issues(
    project_id=5,
    # ... те же параметры
)
# Возвращает: list[Task] - список актуальных задач проекта
```

### Получение доступных родителей

Методы для получения доступных родительских проектов (для выбора родителя при создании или перемещении проекта):

```python
# Глобальный поиск доступных родительских проектов
parents = await client.projects.get_available_parents(
    is_template=False,                        # bool: Фильтр по шаблонам
    limit=10,                                 # int: Количество элементов
)
for parent in parents:
    print(f"Project: {parent.name}")

# Доступные родители для существующего проекта
# Исключает сам проект и его потомков
parents = await client.projects.get_available_parents_for(
    project_id=456,
    is_template=False,
    limit=10,
)
```

**Примечание:** В отличие от задач, проекты могут быть вложены только в другие проекты, поэтому возвращается список объектов `Project`.

### Получение всех участников проекта

Метод `get_all_participants()` возвращает полный список участников проекта в одном запросе:

```python
participants = await client.projects.get_all_participants(
    project_id=123,
    limit=None,                               # int: Количество элементов
)
# Возвращает: list[Employee | ContractorHuman | Group]

for participant in participants:
    print(f"{type(participant).__name__}: {participant.display_name()}")
```

### Получение полной информации о проекте

Метод `get_full_details()` для проектов поддерживает следующие специфичные параметры:

```python
details = await client.projects.get_full_details(
    project_id=5,
    include_deals=True,                    # Загрузить связанные сделки
    include_issues=True,                    # Загрузить задачи проекта
    include_actual_issues=True,            # Загрузить актуальные задачи
    include_comments=True,                  # Загрузить комментарии
    include_history=True,                   # Загрузить историю изменений
    include_auditors=True,                  # Загрузить список аудиторов
    include_executors=True,                 # Загрузить соисполнителей
    include_milestones=True,                # Загрузить вехи
    include_responsible_details=True,        # Загрузить полные данные ответственного
    include_owner_details=True,             # Загрузить полные данные владельца
    comments_limit=50,                      # Лимит комментариев (опционально)
    history_limit=100                      # Лимит записей истории (опционально)
)
```

**Поля объекта ProjectFullDetails:**
- `project: Project` - Основной проект
- `deals: list[Deal] | None` - Связанные сделки
- `issues: list[Task] | None` - Задачи проекта
- `actual_issues: list[Task] | None` - Актуальные задачи
- `comments: list[Comment] | None` - Комментарии
- `history: list[di