megaplan-sdk 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- megaplan_sdk/__init__.py +67 -0
- megaplan_sdk/auth.py +185 -0
- megaplan_sdk/cache.py +192 -0
- megaplan_sdk/client.py +201 -0
- megaplan_sdk/constants.py +16 -0
- megaplan_sdk/exceptions.py +180 -0
- megaplan_sdk/helpers.py +108 -0
- megaplan_sdk/http_client.py +390 -0
- megaplan_sdk/logging_config.py +53 -0
- megaplan_sdk/models/__init__.py +22 -0
- megaplan_sdk/models/base.py +16 -0
- megaplan_sdk/models/comment.py +58 -0
- megaplan_sdk/models/common.py +107 -0
- megaplan_sdk/models/contractor.py +137 -0
- megaplan_sdk/models/deal.py +96 -0
- megaplan_sdk/models/department.py +40 -0
- megaplan_sdk/models/employee.py +117 -0
- megaplan_sdk/models/project.py +76 -0
- megaplan_sdk/models/task.py +75 -0
- megaplan_sdk/resources/__init__.py +15 -0
- megaplan_sdk/resources/auth.py +73 -0
- megaplan_sdk/resources/base.py +794 -0
- megaplan_sdk/resources/comments.py +148 -0
- megaplan_sdk/resources/contractors.py +173 -0
- megaplan_sdk/resources/deals.py +625 -0
- megaplan_sdk/resources/departments.py +70 -0
- megaplan_sdk/resources/employees.py +216 -0
- megaplan_sdk/resources/full_details.py +143 -0
- megaplan_sdk/resources/projects.py +854 -0
- megaplan_sdk/resources/tasks.py +932 -0
- megaplan_sdk/types.py +56 -0
- megaplan_sdk-0.1.0.dist-info/METADATA +1383 -0
- megaplan_sdk-0.1.0.dist-info/RECORD +36 -0
- megaplan_sdk-0.1.0.dist-info/WHEEL +5 -0
- megaplan_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
- megaplan_sdk-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1383 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: megaplan-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Professional Python SDK for Megaplan API v3
|
|
5
|
+
Author-email: Maxim Borzov <max@borzov.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/borzov/megaplan-sdk
|
|
8
|
+
Project-URL: Documentation, https://github.com/borzov/megaplan-sdk#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/borzov/megaplan-sdk
|
|
10
|
+
Project-URL: Issues, https://github.com/borzov/megaplan-sdk/issues
|
|
11
|
+
Keywords: megaplan,crm,api,sdk
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: httpx>=0.25.0
|
|
23
|
+
Requires-Dist: pydantic>=2.0.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
28
|
+
Requires-Dist: respx>=0.20.0; extra == "dev"
|
|
29
|
+
Requires-Dist: mypy>=1.5.0; extra == "dev"
|
|
30
|
+
Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# Megaplan Python SDK
|
|
34
|
+
|
|
35
|
+
[](https://pypi.org/project/megaplan-sdk/)
|
|
36
|
+
[](https://pypi.org/project/megaplan-sdk/)
|
|
37
|
+
[](https://github.com/borzov/megaplan-sdk/actions/workflows/tests.yml)
|
|
38
|
+
[](https://codecov.io/gh/borzov/megaplan-sdk)
|
|
39
|
+
[](https://github.com/charliermarsh/ruff)
|
|
40
|
+
|
|
41
|
+
Профессиональная Python-библиотека для работы с API Мегаплана версии 3.
|
|
42
|
+
|
|
43
|
+
## О проекте
|
|
44
|
+
|
|
45
|
+
Современная библиотека для интеграции с CRM Мегаплан. Она предоставляет удобный и типобезопасный интерфейс для работы с задачами, проектами, сделками и другими сущностями через REST API.
|
|
46
|
+
|
|
47
|
+
### Зачем нужна эта библиотека?
|
|
48
|
+
|
|
49
|
+
Работа с API Мегаплана напрямую требует знания множества технических деталей: правильной настройки OAuth2-авторизации, обработки токенов, формирования JSON-параметров в query string, обработки ошибок и пагинации. Эта библиотека берет на себя всю рутинную работу, позволяя разработчикам сосредоточиться на бизнес-логике.
|
|
50
|
+
|
|
51
|
+
### Преимущества использования SDK
|
|
52
|
+
|
|
53
|
+
Вместо прямых HTTP-запросов вы получаете простой и понятный Python-интерфейс. Вместо ручной работы с токенами — автоматическую авторизацию и обновление токенов. Вместо парсинга JSON-ответов — типизированные Pydantic-модели с автодополнением в IDE. Вместо обработки ошибок вручную — понятные исключения с детальной информацией.
|
|
54
|
+
|
|
55
|
+
Библиотека полностью асинхронная, что позволяет эффективно работать с большими объемами данных и выполнять параллельные запросы. Встроенная логика повторных попыток при временных сбоях сервера делает интеграцию более надежной. Модульная архитектура позволяет легко расширять функциональность и добавлять поддержку новых модулей API.
|
|
56
|
+
|
|
57
|
+
## Возможности
|
|
58
|
+
|
|
59
|
+
- Полный CRUD для задач, проектов и сделок
|
|
60
|
+
- Метод `get_full_details()` — получение сущности со всеми связанными данными (комментарии, история, подзадачи и т.д.) за один вызов с параллельной загрузкой
|
|
61
|
+
- OAuth2-авторизация с автоматическим обновлением токенов
|
|
62
|
+
- Типобезопасность с Pydantic-моделями и полной типизацией
|
|
63
|
+
- Асинхронность — поддержка async/await во всех операциях
|
|
64
|
+
- Автоматические повторы при ошибках сервера (5xx)
|
|
65
|
+
- Модульная архитектура для легкого расширения
|
|
66
|
+
- Комплексные тесты с покрытием 80%+
|
|
67
|
+
|
|
68
|
+
## Установка
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install megaplan-sdk
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Или из исходников:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
git clone https://github.com/borzov/megaplan-sdk.git
|
|
78
|
+
cd megaplan-sdk
|
|
79
|
+
pip install -e .
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Быстрый старт
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
import asyncio
|
|
86
|
+
from megaplan_sdk import MegaplanClient
|
|
87
|
+
|
|
88
|
+
async def main():
|
|
89
|
+
# Создание клиента с учетными данными
|
|
90
|
+
async with MegaplanClient(
|
|
91
|
+
base_url="https://my.megaplan.ru",
|
|
92
|
+
username="user@example.com",
|
|
93
|
+
password="your_password"
|
|
94
|
+
) as client:
|
|
95
|
+
|
|
96
|
+
# Получение списка задач
|
|
97
|
+
tasks = await client.tasks.list(limit=10)
|
|
98
|
+
for task in tasks:
|
|
99
|
+
print(f"Задача: {task.name}")
|
|
100
|
+
|
|
101
|
+
# Получение конкретной задачи
|
|
102
|
+
task = await client.tasks.get(task_id=42)
|
|
103
|
+
print(f"Детали задачи: {task.name}, Статус: {task.status}")
|
|
104
|
+
|
|
105
|
+
# Упрощенное создание задачи
|
|
106
|
+
new_task = await client.tasks.create_simple(
|
|
107
|
+
"Новая задача",
|
|
108
|
+
employees_resource=client.employees
|
|
109
|
+
)
|
|
110
|
+
print(f"Создана задача: {new_task.name}")
|
|
111
|
+
|
|
112
|
+
# Получение задачи со всеми связанными данными за один вызов
|
|
113
|
+
details = await client.tasks.get_full_details(
|
|
114
|
+
task_id=42,
|
|
115
|
+
include_comments=True,
|
|
116
|
+
include_sub_tasks=True,
|
|
117
|
+
include_responsible_details=True
|
|
118
|
+
)
|
|
119
|
+
print(f"Комментариев: {len(details.comments) if details.comments else 0}")
|
|
120
|
+
print(f"Подзадач: {len(details.sub_tasks) if details.sub_tasks else 0}")
|
|
121
|
+
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
asyncio.run(main())
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Авторизация
|
|
127
|
+
|
|
128
|
+
SDK поддерживает OAuth2-авторизацию. Вы можете передать учетные данные или использовать предварительно полученный токен доступа:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
# С логином и паролем (автоматическая авторизация)
|
|
132
|
+
client = MegaplanClient(
|
|
133
|
+
base_url="https://my.megaplan.ru",
|
|
134
|
+
username="user@example.com",
|
|
135
|
+
password="password"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# С токеном доступа
|
|
139
|
+
client = MegaplanClient(
|
|
140
|
+
base_url="https://my.megaplan.ru",
|
|
141
|
+
access_token="your_access_token"
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Helper-функции
|
|
146
|
+
|
|
147
|
+
SDK предоставляет удобные функции для создания BaseEntity объектов:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from megaplan_sdk import (
|
|
151
|
+
make_employee_entity,
|
|
152
|
+
make_project_entity,
|
|
153
|
+
make_task_entity,
|
|
154
|
+
make_deal_entity,
|
|
155
|
+
make_contractor_entity,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Вместо ручного создания {"contentType": "Employee", "id": 123}
|
|
159
|
+
employee_ref = make_employee_entity(123)
|
|
160
|
+
project_ref = make_project_entity(456)
|
|
161
|
+
task_ref = make_task_entity(789)
|
|
162
|
+
deal_ref = make_deal_entity(101)
|
|
163
|
+
contractor_ref = make_contractor_entity(202)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Работа с задачами
|
|
167
|
+
|
|
168
|
+
### Получение списка задач
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
tasks = await client.tasks.list(
|
|
172
|
+
filter=None, # TaskFilter: ID фильтра (int) или конфигурация (dict)
|
|
173
|
+
statuses=None, # list[str]: Статусы задач для фильтрации
|
|
174
|
+
limit=None, # int: Количество элементов на странице
|
|
175
|
+
page_after=None, # dict: Загрузить страницу, начиная с этой сущности
|
|
176
|
+
page_before=None, # dict: Загрузить страницу строго до этой сущности
|
|
177
|
+
page_with=None, # dict: Загрузить страницу с наличием этой сущности
|
|
178
|
+
fields=None, # any: Набор дополнительных полей
|
|
179
|
+
sort_by=None, # list[dict]: Массив полей сортировки
|
|
180
|
+
only_requested_fields=None # bool: Отдавать только перечисленные поля
|
|
181
|
+
)
|
|
182
|
+
# Возвращает: list[Task] - список объектов Task
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Примеры использования:**
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
# Получить все задачи
|
|
189
|
+
tasks = await client.tasks.list()
|
|
190
|
+
|
|
191
|
+
# С фильтром по статусам
|
|
192
|
+
tasks = await client.tasks.list(
|
|
193
|
+
statuses=["assigned", "in_progress"],
|
|
194
|
+
limit=50
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# С фильтром по ID
|
|
198
|
+
tasks = await client.tasks.list(filter=123)
|
|
199
|
+
|
|
200
|
+
# С фильтром по конфигурации
|
|
201
|
+
tasks = await client.tasks.list(filter={"status": "active"})
|
|
202
|
+
|
|
203
|
+
# Итерация по всем задачам с автоматической пагинацией
|
|
204
|
+
async for task in client.tasks.iterate(limit=100):
|
|
205
|
+
print(task.name)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Получение задачи по ID
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
task = await client.tasks.get(task_id=42)
|
|
212
|
+
# Параметры:
|
|
213
|
+
# task_id: int - Идентификатор задачи
|
|
214
|
+
# Возвращает: Task - объект задачи со всеми полями
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Поля объекта Task:**
|
|
218
|
+
- `id: int` - Идентификатор задачи
|
|
219
|
+
- `name: str` - Название задачи
|
|
220
|
+
- `description: str` - Описание
|
|
221
|
+
- `status: str` - Статус задачи
|
|
222
|
+
- `responsible: BaseEntity` - Ответственный (Employee)
|
|
223
|
+
- `owner: BaseEntity` - Владелец (Employee)
|
|
224
|
+
- `deadline: str` - Срок выполнения
|
|
225
|
+
- `actual_finish: str` - Фактическая дата завершения
|
|
226
|
+
- `parent: BaseEntity` - Родительская задача/проект
|
|
227
|
+
- `project: BaseEntity` - Проект
|
|
228
|
+
- `priority: str` - Приоритет
|
|
229
|
+
- `tags: list[BaseEntity]` - Теги
|
|
230
|
+
- `attaches: list[BaseEntity]` - Вложения (файлы)
|
|
231
|
+
- `todos: list[BaseEntity]` - Подзадачи-чеклисты
|
|
232
|
+
- `created_at: str` - Дата создания
|
|
233
|
+
- `updated_at: str` - Дата обновления
|
|
234
|
+
|
|
235
|
+
### Создание задачи
|
|
236
|
+
|
|
237
|
+
#### Упрощенное создание (рекомендуется)
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
# Простое создание задачи с автоматическим заполнением обязательных полей
|
|
241
|
+
# Автоматически устанавливает isUrgent=False, isTemplate=False
|
|
242
|
+
task = await client.tasks.create({"name": "Новая задача"})
|
|
243
|
+
|
|
244
|
+
# Создание задачи с текущим пользователем как ответственным
|
|
245
|
+
task = await client.tasks.create_simple(
|
|
246
|
+
"Новая задача",
|
|
247
|
+
employees_resource=client.employees # Автоматически определит текущего пользователя
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
# Создание задачи с указанным ответственным
|
|
251
|
+
from megaplan_sdk import make_employee_entity
|
|
252
|
+
task = await client.tasks.create_simple(
|
|
253
|
+
"Новая задача",
|
|
254
|
+
responsible_id=123
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# Создание задачи внутри проекта (автоматически устанавливает связь)
|
|
258
|
+
task = await client.tasks.create_in_project(
|
|
259
|
+
"Задача в проекте",
|
|
260
|
+
project_id=456,
|
|
261
|
+
employees_resource=client.employees
|
|
262
|
+
)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
#### Полное создание (для продвинутых случаев)
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
# Использование helper-функций для создания BaseEntity объектов
|
|
269
|
+
from megaplan_sdk import make_employee_entity, make_project_entity, make_task_entity
|
|
270
|
+
|
|
271
|
+
task = await client.tasks.create({
|
|
272
|
+
"name": "Новая задача", # str: Название задачи (обязательно)
|
|
273
|
+
"responsible": make_employee_entity(1), # BaseEntity: Ответственный (helper)
|
|
274
|
+
"deadline": "2024-12-31", # str: Срок выполнения
|
|
275
|
+
"subject": "Описание задачи", # str: Описание
|
|
276
|
+
"parent": make_project_entity(5), # BaseEntity: Родительский проект (helper)
|
|
277
|
+
"priority": "high", # str: Приоритет
|
|
278
|
+
"isUrgent": False, # bool: Горящая (обязательно, но можно не указывать)
|
|
279
|
+
"isTemplate": False, # bool: Шаблон (обязательно, но можно не указывать)
|
|
280
|
+
})
|
|
281
|
+
# Возвращает: Task - созданная задача
|
|
282
|
+
|
|
283
|
+
# Или вручную создавать BaseEntity
|
|
284
|
+
task = await client.tasks.create({
|
|
285
|
+
"name": "Новая задача",
|
|
286
|
+
"responsible": {"contentType": "Employee", "id": 1},
|
|
287
|
+
"parent": {"contentType": "Project", "id": 5},
|
|
288
|
+
})
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Обновление задачи
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
task = await client.tasks.update(
|
|
295
|
+
task_id=42, # int: Идентификатор задачи
|
|
296
|
+
task_data={ # dict: Данные для обновления
|
|
297
|
+
"status": "completed",
|
|
298
|
+
"actualFinish": "2024-01-15",
|
|
299
|
+
"name": "Обновленное название"
|
|
300
|
+
}
|
|
301
|
+
)
|
|
302
|
+
# Возвращает: Task - обновленная задача
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Удаление задачи
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
await client.tasks.delete(task_id=42)
|
|
309
|
+
# Параметры:
|
|
310
|
+
# task_id: int - Идентификатор задачи
|
|
311
|
+
# Возвращает: None
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Получение подзадач
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
subtasks = await client.tasks.get_sub_tasks(
|
|
318
|
+
task_id=10, # int: Идентификатор задачи
|
|
319
|
+
filters=None, # list[dict]: Фильтры типов результатов
|
|
320
|
+
limit=None, # int: Количество элементов
|
|
321
|
+
page_after=None, # dict: Пагинация после
|
|
322
|
+
page_before=None, # dict: Пагинация до
|
|
323
|
+
page_with=None, # dict: Пагинация с
|
|
324
|
+
fields=None, # any: Дополнительные поля
|
|
325
|
+
sort_by=None, # list[dict]: Сортировка
|
|
326
|
+
only_requested_fields=None # bool: Только запрошенные поля
|
|
327
|
+
)
|
|
328
|
+
# Возвращает: list[Task] - список подзадач
|
|
329
|
+
|
|
330
|
+
# Получение актуальных подзадач
|
|
331
|
+
actual_subtasks = await client.tasks.get_actual_sub_tasks(
|
|
332
|
+
task_id=10,
|
|
333
|
+
# ... те же параметры
|
|
334
|
+
)
|
|
335
|
+
# Возвращает: list[Task] - список актуальных подзадач
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Получение задач на уровне дерева
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
tasks = await client.tasks.tree_level(
|
|
342
|
+
filter=None, # TaskFilter: Фильтр
|
|
343
|
+
limit=None, # int: Количество элементов
|
|
344
|
+
page_after=None, # dict: Пагинация
|
|
345
|
+
page_before=None, # dict: Пагинация
|
|
346
|
+
page_with=None, # dict: Пагинация
|
|
347
|
+
fields=None, # any: Дополнительные поля
|
|
348
|
+
sort_by=None, # list[dict]: Сортировка
|
|
349
|
+
only_requested_fields=None # bool: Только запрошенные поля
|
|
350
|
+
)
|
|
351
|
+
# Возвращает: list[Task | Project] - список задач/проектов текущего уровня
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Получение полной информации о задаче
|
|
355
|
+
|
|
356
|
+
Метод `get_full_details()` позволяет получить задачу со всеми связанными данными за один вызов. Все запросы выполняются параллельно для максимальной производительности.
|
|
357
|
+
|
|
358
|
+
```python
|
|
359
|
+
from megaplan_sdk import MegaplanClient
|
|
360
|
+
|
|
361
|
+
async with MegaplanClient(...) as client:
|
|
362
|
+
# Получить задачу со всей информацией
|
|
363
|
+
details = await client.tasks.get_full_details(
|
|
364
|
+
task_id=42,
|
|
365
|
+
include_sub_tasks=True, # Загрузить подзадачи
|
|
366
|
+
include_actual_sub_tasks=True, # Загрузить актуальные подзадачи
|
|
367
|
+
include_comments=True, # Загрузить комментарии
|
|
368
|
+
include_history=True, # Загрузить историю изменений
|
|
369
|
+
include_auditors=True, # Загрузить список аудиторов
|
|
370
|
+
include_executors=True, # Загрузить соисполнителей
|
|
371
|
+
include_milestones=True, # Загрузить вехи
|
|
372
|
+
include_responsible_details=True, # Загрузить полные данные ответственного
|
|
373
|
+
include_owner_details=True, # Загрузить полные данные постановщика
|
|
374
|
+
comments_limit=50, # Лимит комментариев (опционально)
|
|
375
|
+
history_limit=100 # Лимит записей истории (опционально)
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
# Доступ к основным данным задачи
|
|
379
|
+
print(f"Задача: {details.task.name}")
|
|
380
|
+
print(f"Статус: {details.task.status}")
|
|
381
|
+
|
|
382
|
+
# Доступ к связанным данным
|
|
383
|
+
if details.comments:
|
|
384
|
+
print(f"Комментариев: {len(details.comments)}")
|
|
385
|
+
for comment in details.comments:
|
|
386
|
+
print(f" - {comment.text}")
|
|
387
|
+
|
|
388
|
+
if details.sub_tasks:
|
|
389
|
+
print(f"Подзадач: {len(details.sub_tasks)}")
|
|
390
|
+
for subtask in details.sub_tasks:
|
|
391
|
+
print(f" - {subtask.name}")
|
|
392
|
+
|
|
393
|
+
if details.responsible_details:
|
|
394
|
+
print(f"Ответственный: {details.responsible_details.first_name} {details.responsible_details.last_name}")
|
|
395
|
+
|
|
396
|
+
if details.owner_details:
|
|
397
|
+
print(f"Постановщик: {details.owner_details.first_name} {details.owner_details.last_name}")
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
**Поля объекта TaskFullDetails:**
|
|
401
|
+
- `task: Task` - Основная задача
|
|
402
|
+
- `sub_tasks: list[Task] | None` - Подзадачи
|
|
403
|
+
- `actual_sub_tasks: list[Task] | None` - Актуальные подзадачи
|
|
404
|
+
- `comments: list[Comment] | None` - Комментарии
|
|
405
|
+
- `history: list[dict] | None` - История изменений
|
|
406
|
+
- `auditors: list[dict] | None` - Аудиторы
|
|
407
|
+
- `executors: list[dict] | None` - Соисполнители
|
|
408
|
+
- `milestones: list[dict] | None` - Вехи
|
|
409
|
+
- `responsible_details: Employee | None` - Полные данные ответственного
|
|
410
|
+
- `owner_details: Employee | None` - Полные данные постановщика
|
|
411
|
+
|
|
412
|
+
**Примеры использования:**
|
|
413
|
+
|
|
414
|
+
```python
|
|
415
|
+
# Минимальный вызов - только основная задача
|
|
416
|
+
details = await client.tasks.get_full_details(task_id=42)
|
|
417
|
+
|
|
418
|
+
# Задача с комментариями и историей
|
|
419
|
+
details = await client.tasks.get_full_details(
|
|
420
|
+
task_id=42,
|
|
421
|
+
include_comments=True,
|
|
422
|
+
include_history=True,
|
|
423
|
+
comments_limit=20
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
# Полная информация для отчета
|
|
427
|
+
details = await client.tasks.get_full_details(
|
|
428
|
+
task_id=42,
|
|
429
|
+
include_sub_tasks=True,
|
|
430
|
+
include_comments=True,
|
|
431
|
+
include_history=True,
|
|
432
|
+
include_auditors=True,
|
|
433
|
+
include_executors=True,
|
|
434
|
+
include_responsible_details=True,
|
|
435
|
+
include_owner_details=True
|
|
436
|
+
)
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
## Работа с проектами
|
|
440
|
+
|
|
441
|
+
### Получение списка проектов
|
|
442
|
+
|
|
443
|
+
```python
|
|
444
|
+
projects = await client.projects.list(
|
|
445
|
+
limit=None, # int: Количество элементов на странице
|
|
446
|
+
page_after=None, # dict: Загрузить страницу, начиная с этой сущности
|
|
447
|
+
page_before=None, # dict: Загрузить страницу строго до этой сущности
|
|
448
|
+
page_with=None, # dict: Загрузить страницу с наличием этой сущности
|
|
449
|
+
fields=None, # any: Набор дополнительных полей
|
|
450
|
+
sort_by=None, # list[dict]: Массив полей сортировки
|
|
451
|
+
only_requested_fields=None # bool: Отдавать только перечисленные поля
|
|
452
|
+
)
|
|
453
|
+
# Возвращает: list[Project] - список объектов Project
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### Получение проекта по ID
|
|
457
|
+
|
|
458
|
+
```python
|
|
459
|
+
project = await client.projects.get(project_id=5)
|
|
460
|
+
# Параметры:
|
|
461
|
+
# project_id: int - Идентификатор проекта
|
|
462
|
+
# Возвращает: Project - объект проекта со всеми полями
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
**Поля объекта Project:**
|
|
466
|
+
- `id: int` - Идентификатор проекта
|
|
467
|
+
- `name: str` - Название проекта
|
|
468
|
+
- `description: str` - Описание
|
|
469
|
+
- `status: str` - Статус проекта
|
|
470
|
+
- `owner: BaseEntity` - Владелец (Employee)
|
|
471
|
+
- `responsible: BaseEntity` - Ответственный (Employee)
|
|
472
|
+
- `deadline: str` - Срок выполнения
|
|
473
|
+
- `actual_finish: str` - Фактическая дата завершения
|
|
474
|
+
- `parent: BaseEntity` - Родительский проект
|
|
475
|
+
- `priority: str` - Приоритет
|
|
476
|
+
- `tags: list[BaseEntity]` - Теги
|
|
477
|
+
- `attaches: list[BaseEntity]` - Вложения
|
|
478
|
+
- `todos: list[BaseEntity]` - Подзадачи-чеклисты
|
|
479
|
+
- `created_at: str` - Дата создания
|
|
480
|
+
- `updated_at: str` - Дата обновления
|
|
481
|
+
|
|
482
|
+
### Создание проекта
|
|
483
|
+
|
|
484
|
+
#### Упрощенное создание (рекомендуется)
|
|
485
|
+
|
|
486
|
+
```python
|
|
487
|
+
# Простое создание проекта с автоматическим заполнением обязательных полей
|
|
488
|
+
# Автоматически устанавливает isTemplate=False
|
|
489
|
+
project = await client.projects.create({"name": "Новый проект"})
|
|
490
|
+
|
|
491
|
+
# Создание проекта с текущим пользователем как владельцем и ответственным
|
|
492
|
+
project = await client.projects.create_simple(
|
|
493
|
+
"Новый проект",
|
|
494
|
+
employees_resource=client.employees # Автоматически определит текущего пользователя
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
# Создание проекта с указанными владельцем и ответственным
|
|
498
|
+
project = await client.projects.create_simple(
|
|
499
|
+
"Новый проект",
|
|
500
|
+
owner_id=123,
|
|
501
|
+
responsible_id=123
|
|
502
|
+
)
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
#### Полное создание (для продвинутых случаев)
|
|
506
|
+
|
|
507
|
+
```python
|
|
508
|
+
# Использование helper-функций
|
|
509
|
+
from megaplan_sdk import make_employee_entity
|
|
510
|
+
|
|
511
|
+
project = await client.projects.create({
|
|
512
|
+
"name": "Новый проект", # str: Название проекта (обязательно)
|
|
513
|
+
"owner": make_employee_entity(1), # BaseEntity: Владелец (helper)
|
|
514
|
+
"responsible": make_employee_entity(2), # BaseEntity: Ответственный (helper)
|
|
515
|
+
"deadline": "2024-12-31", # str: Срок выполнения
|
|
516
|
+
"description": "Описание проекта", # str: Описание
|
|
517
|
+
"isTemplate": False, # bool: Шаблон (обязательно, но можно не указывать)
|
|
518
|
+
})
|
|
519
|
+
# Возвращает: Project - созданный проект
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
### Обновление проекта
|
|
523
|
+
|
|
524
|
+
```python
|
|
525
|
+
project = await client.projects.update(
|
|
526
|
+
project_id=5, # int: Идентификатор проекта
|
|
527
|
+
project_data={ # dict: Данные для обновления
|
|
528
|
+
"name": "Обновленное название",
|
|
529
|
+
"status": "in_progress"
|
|
530
|
+
}
|
|
531
|
+
)
|
|
532
|
+
# Возвращает: Project - обновленный проект
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### Удаление проекта
|
|
536
|
+
|
|
537
|
+
```python
|
|
538
|
+
await client.projects.delete(project_id=5)
|
|
539
|
+
# Параметры:
|
|
540
|
+
# project_id: int - Идентификатор проекта
|
|
541
|
+
# Возвращает: None
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### Получение сделок проекта
|
|
545
|
+
|
|
546
|
+
```python
|
|
547
|
+
deals = await client.projects.get_deals(
|
|
548
|
+
project_id=5, # int: Идентификатор проекта
|
|
549
|
+
limit=None, # int: Количество элементов
|
|
550
|
+
page_after=None, # dict: Пагинация после
|
|
551
|
+
page_before=None, # dict: Пагинация до
|
|
552
|
+
page_with=None, # dict: Пагинация с
|
|
553
|
+
fields=None, # any: Дополнительные поля
|
|
554
|
+
sort_by=None, # list[dict]: Сортировка
|
|
555
|
+
only_requested_fields=None # bool: Только запрошенные поля
|
|
556
|
+
)
|
|
557
|
+
# Возвращает: list[Deal] - список связанных сделок
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### Получение задач проекта
|
|
561
|
+
|
|
562
|
+
```python
|
|
563
|
+
issues = await client.projects.get_issues(
|
|
564
|
+
project_id=5, # int: Идентификатор проекта
|
|
565
|
+
limit=None, # int: Количество элементов
|
|
566
|
+
page_after=None, # dict: Пагинация
|
|
567
|
+
page_before=None, # dict: Пагинация
|
|
568
|
+
page_with=None, # dict: Пагинация
|
|
569
|
+
fields=None, # any: Дополнительные поля
|
|
570
|
+
sort_by=None, # list[dict]: Сортировка
|
|
571
|
+
only_requested_fields=None # bool: Только запрошенные поля
|
|
572
|
+
)
|
|
573
|
+
# Возвращает: list[Task] - список задач проекта
|
|
574
|
+
|
|
575
|
+
# Получение актуальных задач проекта
|
|
576
|
+
actual_issues = await client.projects.get_actual_issues(
|
|
577
|
+
project_id=5,
|
|
578
|
+
# ... те же параметры
|
|
579
|
+
)
|
|
580
|
+
# Возвращает: list[Task] - список актуальных задач проекта
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### Получение полной информации о проекте
|
|
584
|
+
|
|
585
|
+
Метод `get_full_details()` позволяет получить проект со всеми связанными данными за один вызов. Все запросы выполняются параллельно для максимальной производительности.
|
|
586
|
+
|
|
587
|
+
```python
|
|
588
|
+
from megaplan_sdk import MegaplanClient
|
|
589
|
+
|
|
590
|
+
async with MegaplanClient(...) as client:
|
|
591
|
+
# Получить проект со всей информацией
|
|
592
|
+
details = await client.projects.get_full_details(
|
|
593
|
+
project_id=5,
|
|
594
|
+
include_deals=True, # Загрузить связанные сделки
|
|
595
|
+
include_issues=True, # Загрузить задачи проекта
|
|
596
|
+
include_actual_issues=True, # Загрузить актуальные задачи
|
|
597
|
+
include_comments=True, # Загрузить комментарии
|
|
598
|
+
include_history=True, # Загрузить историю изменений
|
|
599
|
+
include_auditors=True, # Загрузить список аудиторов
|
|
600
|
+
include_executors=True, # Загрузить соисполнителей
|
|
601
|
+
include_milestones=True, # Загрузить вехи
|
|
602
|
+
include_responsible_details=True, # Загрузить полные данные ответственного
|
|
603
|
+
include_owner_details=True, # Загрузить полные данные владельца
|
|
604
|
+
comments_limit=50, # Лимит комментариев (опционально)
|
|
605
|
+
history_limit=100 # Лимит записей истории (опционально)
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
# Доступ к основным данным проекта
|
|
609
|
+
print(f"Проект: {details.project.name}")
|
|
610
|
+
print(f"Статус: {details.project.status}")
|
|
611
|
+
|
|
612
|
+
# Доступ к связанным данным
|
|
613
|
+
if details.deals:
|
|
614
|
+
print(f"Сделок: {len(details.deals)}")
|
|
615
|
+
for deal in details.deals:
|
|
616
|
+
print(f" - {deal.name}")
|
|
617
|
+
|
|
618
|
+
if details.issues:
|
|
619
|
+
print(f"Задач: {len(details.issues)}")
|
|
620
|
+
for task in details.issues:
|
|
621
|
+
print(f" - {task.name}")
|
|
622
|
+
|
|
623
|
+
if details.comments:
|
|
624
|
+
print(f"Комментариев: {len(details.comments)}")
|
|
625
|
+
|
|
626
|
+
if details.responsible_details:
|
|
627
|
+
print(f"Ответственный: {details.responsible_details.first_name} {details.responsible_details.last_name}")
|
|
628
|
+
|
|
629
|
+
if details.owner_details:
|
|
630
|
+
print(f"Владелец: {details.owner_details.first_name} {details.owner_details.last_name}")
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
**Поля объекта ProjectFullDetails:**
|
|
634
|
+
- `project: Project` - Основной проект
|
|
635
|
+
- `deals: list[Deal] | None` - Связанные сделки
|
|
636
|
+
- `issues: list[Task] | None` - Задачи проекта
|
|
637
|
+
- `actual_issues: list[Task] | None` - Актуальные задачи
|
|
638
|
+
- `comments: list[Comment] | None` - Комментарии
|
|
639
|
+
- `history: list[dict] | None` - История изменений
|
|
640
|
+
- `auditors: list[dict] | None` - Аудиторы
|
|
641
|
+
- `executors: list[dict] | None` - Соисполнители
|
|
642
|
+
- `milestones: list[dict] | None` - Вехи
|
|
643
|
+
- `responsible_details: Employee | None` - Полные данные ответственного
|
|
644
|
+
- `owner_details: Employee | None` - Полные данные владельца
|
|
645
|
+
|
|
646
|
+
**Примеры использования:**
|
|
647
|
+
|
|
648
|
+
```python
|
|
649
|
+
# Минимальный вызов - только основной проект
|
|
650
|
+
details = await client.projects.get_full_details(project_id=5)
|
|
651
|
+
|
|
652
|
+
# Проект со сделками и задачами
|
|
653
|
+
details = await client.projects.get_full_details(
|
|
654
|
+
project_id=5,
|
|
655
|
+
include_deals=True,
|
|
656
|
+
include_issues=True
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
# Полная информация для отчета
|
|
660
|
+
details = await client.projects.get_full_details(
|
|
661
|
+
project_id=5,
|
|
662
|
+
include_deals=True,
|
|
663
|
+
include_issues=True,
|
|
664
|
+
include_comments=True,
|
|
665
|
+
include_history=True,
|
|
666
|
+
include_responsible_details=True,
|
|
667
|
+
include_owner_details=True
|
|
668
|
+
)
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
## Работа со сделками
|
|
672
|
+
|
|
673
|
+
### Получение списка сделок
|
|
674
|
+
|
|
675
|
+
```python
|
|
676
|
+
deals = await client.deals.list(
|
|
677
|
+
filter=None, # TradeFilter: ID фильтра (int) или конфигурация (dict)
|
|
678
|
+
status=None, # ProgramState: Статус программы для фильтрации
|
|
679
|
+
q=None, # str: Поисковый запрос
|
|
680
|
+
base_on=None, # BaseEntity: Базовая сущность для фильтрации
|
|
681
|
+
limit=None, # int: Количество элементов на странице
|
|
682
|
+
page_after=None, # dict: Пагинация после
|
|
683
|
+
page_before=None, # dict: Пагинация до
|
|
684
|
+
page_with=None, # dict: Пагинация с
|
|
685
|
+
fields=None, # any: Дополнительные поля
|
|
686
|
+
sort_by=None, # list[dict]: Сортировка
|
|
687
|
+
only_requested_fields=None # bool: Только запрошенные поля
|
|
688
|
+
)
|
|
689
|
+
# Возвращает: list[Deal] - список объектов Deal
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
### Получение сделки по ID
|
|
693
|
+
|
|
694
|
+
```python
|
|
695
|
+
deal = await client.deals.get(deal_id=200)
|
|
696
|
+
# Параметры:
|
|
697
|
+
# deal_id: int - Идентификатор сделки
|
|
698
|
+
# Возвращает: Deal - объект сделки со всеми полями
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
**Поля объекта Deal:**
|
|
702
|
+
- `id: int` - Идентификатор сделки
|
|
703
|
+
- `name: str` - Название сделки
|
|
704
|
+
- `program: BaseEntity` - Программа (схема сделки)
|
|
705
|
+
- `state: ProgramState` - Текущий статус в программе
|
|
706
|
+
- `contractor: BaseEntity` - Контрагент (ContractorCompany/ContractorHuman)
|
|
707
|
+
- `responsible: BaseEntity` - Ответственный (Employee)
|
|
708
|
+
- `sum_base: float` - Сумма сделки
|
|
709
|
+
- `currency: BaseEntity` - Валюта
|
|
710
|
+
- `deadline: str` - Срок
|
|
711
|
+
- `description: str` - Описание
|
|
712
|
+
- `tags: list[BaseEntity]` - Теги
|
|
713
|
+
- `attaches: list[BaseEntity]` - Вложения
|
|
714
|
+
- `created_at: str` - Дата создания
|
|
715
|
+
- `updated_at: str` - Дата обновления
|
|
716
|
+
|
|
717
|
+
### Создание сделки
|
|
718
|
+
|
|
719
|
+
```python
|
|
720
|
+
deal = await client.deals.create(deal_data={
|
|
721
|
+
"program": { # BaseEntity: Программа (обязательно)
|
|
722
|
+
"contentType": "Program",
|
|
723
|
+
"id": 10
|
|
724
|
+
},
|
|
725
|
+
"name": "Новая сделка", # str: Название сделки (обязательно)
|
|
726
|
+
"contractor": { # BaseEntity: Контрагент
|
|
727
|
+
"contentType": "ContractorCompany",
|
|
728
|
+
"id": 100
|
|
729
|
+
},
|
|
730
|
+
"responsible": { # BaseEntity: Ответственный
|
|
731
|
+
"contentType": "Employee",
|
|
732
|
+
"id": 1
|
|
733
|
+
},
|
|
734
|
+
"sum_base": 50000.0, # float: Сумма сделки
|
|
735
|
+
"deadline": "2024-12-31", # str: Срок
|
|
736
|
+
"description": "Описание сделки" # str: Описание
|
|
737
|
+
})
|
|
738
|
+
# Возвращает: Deal - созданная сделка
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
### Обновление сделки
|
|
742
|
+
|
|
743
|
+
```python
|
|
744
|
+
deal = await client.deals.update(
|
|
745
|
+
deal_id=200, # int: Идентификатор сделки
|
|
746
|
+
deal_data={ # dict: Данные для обновления
|
|
747
|
+
"sum_base": 60000.0,
|
|
748
|
+
"status": "active"
|
|
749
|
+
}
|
|
750
|
+
)
|
|
751
|
+
# Возвращает: Deal - обновленная сделка
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
### Удаление сделки
|
|
755
|
+
|
|
756
|
+
```python
|
|
757
|
+
await client.deals.delete(deal_id=200)
|
|
758
|
+
# Параметры:
|
|
759
|
+
# deal_id: int - Идентификатор сделки
|
|
760
|
+
# Возвращает: None
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
### Применение перехода (изменение статуса)
|
|
764
|
+
|
|
765
|
+
```python
|
|
766
|
+
deal = await client.deals.apply_transition(
|
|
767
|
+
deal_id=200, # int: Идентификатор сделки
|
|
768
|
+
transition_id=5 # int: Идентификатор перехода
|
|
769
|
+
)
|
|
770
|
+
# Возвращает: Deal - обновленная сделка с новым статусом
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
### Применение триггера
|
|
774
|
+
|
|
775
|
+
```python
|
|
776
|
+
deal = await client.deals.apply_trigger(
|
|
777
|
+
deal_id=200, # int: Идентификатор сделки
|
|
778
|
+
trigger_id=3 # int: Идентификатор триггера
|
|
779
|
+
)
|
|
780
|
+
# Возвращает: Deal - обновленная сделка
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
### Получение аудиторов сделки
|
|
784
|
+
|
|
785
|
+
```python
|
|
786
|
+
auditors = await client.deals.get_auditors(deal_id=200)
|
|
787
|
+
# Параметры:
|
|
788
|
+
# deal_id: int - Идентификатор сделки
|
|
789
|
+
# Возвращает: list[dict] - список аудиторов
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
### Получение истории изменения статуса
|
|
793
|
+
|
|
794
|
+
```python
|
|
795
|
+
history = await client.deals.get_status_history(deal_id=200)
|
|
796
|
+
# Параметры:
|
|
797
|
+
# deal_id: int - Идентификатор сделки
|
|
798
|
+
# Возвращает: list[dict] - список записей истории статусов
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
### Проверка существования сделки
|
|
802
|
+
|
|
803
|
+
```python
|
|
804
|
+
exists = await client.deals.check_exists(deal_params={
|
|
805
|
+
"name": "Название сделки",
|
|
806
|
+
"contractor": {"contentType": "ContractorCompany", "id": 100}
|
|
807
|
+
# ... другие параметры для проверки
|
|
808
|
+
})
|
|
809
|
+
# Параметры:
|
|
810
|
+
# deal_params: dict - Параметры для проверки
|
|
811
|
+
# Возвращает: bool - True если сделка существует, False иначе
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
### Получение полной информации о сделке
|
|
815
|
+
|
|
816
|
+
Метод `get_full_details()` позволяет получить сделку со всеми связанными данными за один вызов. Все запросы выполняются параллельно для максимальной производительности.
|
|
817
|
+
|
|
818
|
+
```python
|
|
819
|
+
from megaplan_sdk import MegaplanClient
|
|
820
|
+
|
|
821
|
+
async with MegaplanClient(...) as client:
|
|
822
|
+
# Получить сделку со всей информацией
|
|
823
|
+
details = await client.deals.get_full_details(
|
|
824
|
+
deal_id=200,
|
|
825
|
+
include_comments=True, # Загрузить комментарии
|
|
826
|
+
include_history=True, # Загрузить историю изменений
|
|
827
|
+
include_status_history=True, # Загрузить историю статусов
|
|
828
|
+
include_auditors=True, # Загрузить список аудиторов
|
|
829
|
+
include_responsible_details=True, # Загрузить полные данные ответственного
|
|
830
|
+
include_contractor_details=True, # Загрузить полные данные контрагента
|
|
831
|
+
include_related_tasks=True, # Загрузить связанные задачи
|
|
832
|
+
comments_limit=50, # Лимит комментариев (опционально)
|
|
833
|
+
history_limit=100 # Лимит записей истории (опционально)
|
|
834
|
+
)
|
|
835
|
+
|
|
836
|
+
# Доступ к основным данным сделки
|
|
837
|
+
print(f"Сделка: {details.deal.name}")
|
|
838
|
+
print(f"Сумма: {details.deal.sum_base}")
|
|
839
|
+
print(f"Статус: {details.deal.state.name if details.deal.state else 'Не указан'}")
|
|
840
|
+
|
|
841
|
+
# Доступ к связанным данным
|
|
842
|
+
if details.comments:
|
|
843
|
+
print(f"Комментариев: {len(details.comments)}")
|
|
844
|
+
for comment in details.comments:
|
|
845
|
+
print(f" - {comment.text}")
|
|
846
|
+
|
|
847
|
+
if details.history:
|
|
848
|
+
print(f"Записей в истории: {len(details.history)}")
|
|
849
|
+
|
|
850
|
+
if details.status_history:
|
|
851
|
+
print(f"Изменений статуса: {len(details.status_history)}")
|
|
852
|
+
|
|
853
|
+
if details.responsible_details:
|
|
854
|
+
print(f"Ответственный: {details.responsible_details.first_name} {details.responsible_details.last_name}")
|
|
855
|
+
print(f"Email: {details.responsible_details.email}")
|
|
856
|
+
|
|
857
|
+
if details.contractor_details:
|
|
858
|
+
print(f"Контрагент: {details.contractor_details.name}")
|
|
859
|
+
|
|
860
|
+
if details.related_tasks:
|
|
861
|
+
print(f"Связанных задач: {len(details.related_tasks)}")
|
|
862
|
+
for task in details.related_tasks:
|
|
863
|
+
print(f" - {task.name}")
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
**Поля объекта DealFullDetails:**
|
|
867
|
+
- `deal: Deal` - Основная сделка
|
|
868
|
+
- `comments: list[Comment] | None` - Комментарии
|
|
869
|
+
- `history: list[dict] | None` - История изменений
|
|
870
|
+
- `status_history: list[dict] | None` - История статусов
|
|
871
|
+
- `auditors: list[dict] | None` - Аудиторы
|
|
872
|
+
- `responsible_details: Employee | None` - Полные данные ответственного
|
|
873
|
+
- `contractor_details: Contractor | None` - Полные данные контрагента
|
|
874
|
+
- `related_tasks: list[Task] | None` - Связанные задачи
|
|
875
|
+
|
|
876
|
+
**Примеры использования:**
|
|
877
|
+
|
|
878
|
+
```python
|
|
879
|
+
# Минимальный вызов - только основная сделка
|
|
880
|
+
details = await client.deals.get_full_details(deal_id=200)
|
|
881
|
+
|
|
882
|
+
# Сделка с комментариями и историей
|
|
883
|
+
details = await client.deals.get_full_details(
|
|
884
|
+
deal_id=200,
|
|
885
|
+
include_comments=True,
|
|
886
|
+
include_history=True,
|
|
887
|
+
include_status_history=True,
|
|
888
|
+
comments_limit=20
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
# Полная информация для отчета
|
|
892
|
+
details = await client.deals.get_full_details(
|
|
893
|
+
deal_id=200,
|
|
894
|
+
include_comments=True,
|
|
895
|
+
include_history=True,
|
|
896
|
+
include_status_history=True,
|
|
897
|
+
include_auditors=True,
|
|
898
|
+
include_responsible_details=True,
|
|
899
|
+
include_contractor_details=True,
|
|
900
|
+
include_related_tasks=True
|
|
901
|
+
)
|
|
902
|
+
|
|
903
|
+
# Только данные о людях
|
|
904
|
+
details = await client.deals.get_full_details(
|
|
905
|
+
deal_id=200,
|
|
906
|
+
include_responsible_details=True,
|
|
907
|
+
include_contractor_details=True
|
|
908
|
+
)
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
## Обработка ошибок
|
|
912
|
+
|
|
913
|
+
SDK предоставляет специфичные типы исключений для различных сценариев ошибок:
|
|
914
|
+
|
|
915
|
+
```python
|
|
916
|
+
from megaplan_sdk import (
|
|
917
|
+
AuthenticationError, # 401 - Ошибка аутентификации
|
|
918
|
+
AuthorizationError, # 403 - Ошибка авторизации (нет прав)
|
|
919
|
+
NotFoundError, # 404 - Ресурс не найден
|
|
920
|
+
ValidationError, # 422 - Ошибка валидации запроса
|
|
921
|
+
RateLimitError, # 429 - Превышен лимит запросов
|
|
922
|
+
ServerError # 5xx - Ошибка сервера
|
|
923
|
+
)
|
|
924
|
+
|
|
925
|
+
try:
|
|
926
|
+
task = await client.tasks.get(task_id=999)
|
|
927
|
+
except NotFoundError:
|
|
928
|
+
print("Задача не найдена")
|
|
929
|
+
except AuthenticationError:
|
|
930
|
+
print("Ошибка аутентификации")
|
|
931
|
+
except ValidationError as e:
|
|
932
|
+
print(f"Ошибки валидации: {e.errors}")
|
|
933
|
+
# e.errors содержит список ошибок из API
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
## Продвинутое использование
|
|
937
|
+
|
|
938
|
+
### Настройка HTTP-клиента
|
|
939
|
+
|
|
940
|
+
```python
|
|
941
|
+
client = MegaplanClient(
|
|
942
|
+
base_url="https://my.megaplan.ru",
|
|
943
|
+
username="user@example.com",
|
|
944
|
+
password="password",
|
|
945
|
+
timeout=60.0, # float: Таймаут запросов в секундах (по умолчанию 30.0)
|
|
946
|
+
max_retries=5 # int: Максимальное количество повторов при 5xx ошибках (по умолчанию 3)
|
|
947
|
+
)
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
### Ручное управление токенами
|
|
951
|
+
|
|
952
|
+
```python
|
|
953
|
+
# Получить токен доступа
|
|
954
|
+
token = await client.auth.authenticate("user@example.com", "password")
|
|
955
|
+
# Возвращает: str - access_token
|
|
956
|
+
|
|
957
|
+
# Обновить токен
|
|
958
|
+
new_token = await client.auth.refresh_token(refresh_token="refresh_token")
|
|
959
|
+
# Параметры:
|
|
960
|
+
# refresh_token: str | None - Токен обновления (опционально, используется сохраненный)
|
|
961
|
+
# Возвращает: str - новый access_token
|
|
962
|
+
|
|
963
|
+
# Установить токен вручную
|
|
964
|
+
client.set_access_token("your_token")
|
|
965
|
+
|
|
966
|
+
# Очистить токены
|
|
967
|
+
client.auth.clear_tokens()
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
## Кэширование сущностей
|
|
971
|
+
|
|
972
|
+
SDK автоматически кэширует справочные сущности (сотрудники, контрагенты, отделы) для уменьшения количества API запросов и повышения производительности.
|
|
973
|
+
|
|
974
|
+
### Включение кэша
|
|
975
|
+
|
|
976
|
+
```python
|
|
977
|
+
async with MegaplanClient(
|
|
978
|
+
base_url="https://my.megaplan.ru",
|
|
979
|
+
username="user@example.com",
|
|
980
|
+
password="password",
|
|
981
|
+
enable_cache=True, # Включить кэш (по умолчанию True)
|
|
982
|
+
cache_ttl=300, # Время жизни кэша: 5 минут (по умолчанию)
|
|
983
|
+
cache_max_size=1000, # Макс. размер кэша: 1000 сущностей (по умолчанию)
|
|
984
|
+
) as client:
|
|
985
|
+
# Кэш работает автоматически при использовании expand
|
|
986
|
+
tasks_full = await client.tasks.list(limit=10, expand=["responsible", "owner"])
|
|
987
|
+
|
|
988
|
+
# Повторная загрузка тех же сотрудников использует кэш
|
|
989
|
+
tasks_full_2 = await client.tasks.list(limit=10, expand=["responsible"])
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
### Управление кэшем
|
|
993
|
+
|
|
994
|
+
```python
|
|
995
|
+
# Очистить весь кэш
|
|
996
|
+
client.clear_cache()
|
|
997
|
+
|
|
998
|
+
# Очистить кэш для конкретного типа сущностей
|
|
999
|
+
client.clear_cache_type("Employee")
|
|
1000
|
+
client.clear_cache_type("Department")
|
|
1001
|
+
client.clear_cache_type("Contractor")
|
|
1002
|
+
|
|
1003
|
+
# Получить статистику кэша
|
|
1004
|
+
if client._cache:
|
|
1005
|
+
stats = client._cache.stats()
|
|
1006
|
+
print(f"Кэшировано сущностей: {stats['size']}")
|
|
1007
|
+
print(f"Типы: {stats['types']}") # {"Employee": 15, "Department": 3}
|
|
1008
|
+
```
|
|
1009
|
+
|
|
1010
|
+
### Особенности кэширования
|
|
1011
|
+
|
|
1012
|
+
- При достижении `cache_max_size` удаляются наименее используемые сущности
|
|
1013
|
+
- Сущности автоматически удаляются из кэша через `cache_ttl` секунд
|
|
1014
|
+
- Кэш работает автоматически, не требуя изменений в коде
|
|
1015
|
+
- При использовании `expand` уникальные сущности загружаются параллельно
|
|
1016
|
+
|
|
1017
|
+
## Глобальные дефолтные лимиты
|
|
1018
|
+
|
|
1019
|
+
SDK позволяет задать глобальные дефолтные значения для параметров `comments_limit` и `history_limit` на уровне клиента. Эти значения будут применяться ко всем вызовам `get_full_details()` для задач, проектов и сделок, если не переопределены явно.
|
|
1020
|
+
|
|
1021
|
+
### Установка глобальных дефолтов
|
|
1022
|
+
|
|
1023
|
+
```python
|
|
1024
|
+
async with MegaplanClient(
|
|
1025
|
+
base_url="https://my.megaplan.ru",
|
|
1026
|
+
username="user@example.com",
|
|
1027
|
+
password="password",
|
|
1028
|
+
default_comments_limit=50, # Дефолт для комментариев
|
|
1029
|
+
default_history_limit=100, # Дефолт для истории
|
|
1030
|
+
) as client:
|
|
1031
|
+
# Использует дефолты (50 комментариев, 100 записей истории)
|
|
1032
|
+
details = await client.tasks.get_full_details(
|
|
1033
|
+
task_id=123,
|
|
1034
|
+
include_comments=True,
|
|
1035
|
+
include_history=True,
|
|
1036
|
+
)
|
|
1037
|
+
|
|
1038
|
+
# Явный параметр переопределяет глобальный дефолт
|
|
1039
|
+
details = await client.tasks.get_full_details(
|
|
1040
|
+
task_id=456,
|
|
1041
|
+
include_comments=True,
|
|
1042
|
+
comments_limit=10, # Используется 10, а не дефолт 50
|
|
1043
|
+
)
|
|
1044
|
+
|
|
1045
|
+
# Без указания лимита API использует свой дефолт
|
|
1046
|
+
details = await client.projects.get_full_details(
|
|
1047
|
+
project_id=5,
|
|
1048
|
+
include_comments=True,
|
|
1049
|
+
# comments_limit не указан, используется глобальный дефолт 50
|
|
1050
|
+
)
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
### Приоритет значений
|
|
1054
|
+
|
|
1055
|
+
Система применяет лимиты в следующем порядке (от высшего к низшему приоритету):
|
|
1056
|
+
|
|
1057
|
+
1. **Явно указанный параметр** в методе `get_full_details()` - всегда имеет наивысший приоритет
|
|
1058
|
+
2. **Глобальный дефолт** из `MegaplanClient` - применяется если параметр не указан явно
|
|
1059
|
+
3. **API default** (`None`) - API использует свои дефолты (обычно без ограничения) если не установлен глобальный дефолт
|
|
1060
|
+
|
|
1061
|
+
```python
|
|
1062
|
+
# Пример приоритетов
|
|
1063
|
+
client = MegaplanClient(
|
|
1064
|
+
base_url="https://my.megaplan.ru",
|
|
1065
|
+
access_token="token",
|
|
1066
|
+
default_comments_limit=50, # Глобальный дефолт
|
|
1067
|
+
)
|
|
1068
|
+
|
|
1069
|
+
# Приоритет 1: Явный параметр (загрузит 100)
|
|
1070
|
+
details = await client.tasks.get_full_details(
|
|
1071
|
+
task_id=1,
|
|
1072
|
+
include_comments=True,
|
|
1073
|
+
comments_limit=100, # Явно указано
|
|
1074
|
+
)
|
|
1075
|
+
|
|
1076
|
+
# Приоритет 2: Глобальный дефолт (загрузит 50)
|
|
1077
|
+
details = await client.tasks.get_full_details(
|
|
1078
|
+
task_id=2,
|
|
1079
|
+
include_comments=True,
|
|
1080
|
+
# comments_limit не указан, используется дефолт 50
|
|
1081
|
+
)
|
|
1082
|
+
|
|
1083
|
+
# Приоритет 3: API default без глобального дефолта
|
|
1084
|
+
client_no_defaults = MegaplanClient(
|
|
1085
|
+
base_url="https://my.megaplan.ru",
|
|
1086
|
+
access_token="token",
|
|
1087
|
+
# default_comments_limit не установлен
|
|
1088
|
+
)
|
|
1089
|
+
details = await client_no_defaults.tasks.get_full_details(
|
|
1090
|
+
task_id=3,
|
|
1091
|
+
include_comments=True,
|
|
1092
|
+
# API использует свой дефолт (обычно без ограничения)
|
|
1093
|
+
)
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
### Когда использовать глобальные дефолты
|
|
1097
|
+
|
|
1098
|
+
Глобальные дефолты полезны в следующих случаях:
|
|
1099
|
+
|
|
1100
|
+
- Ограничение объема загружаемых данных для ускорения запросов
|
|
1101
|
+
- Уменьшение размера ответов API для экономии трафика
|
|
1102
|
+
- Применение одинаковых лимитов ко всем операциям без дублирования кода
|
|
1103
|
+
- Предотвращение загрузки слишком большого количества комментариев/истории
|
|
1104
|
+
|
|
1105
|
+
```python
|
|
1106
|
+
# Пример для высоконагруженного приложения
|
|
1107
|
+
client = MegaplanClient(
|
|
1108
|
+
base_url="https://my.megaplan.ru",
|
|
1109
|
+
access_token="token",
|
|
1110
|
+
default_comments_limit=20, # Ограничение для быстрых ответов
|
|
1111
|
+
default_history_limit=50, # Контроль объема данных
|
|
1112
|
+
)
|
|
1113
|
+
|
|
1114
|
+
# Все вызовы автоматически используют лимиты
|
|
1115
|
+
tasks_details = await client.tasks.get_full_details(
|
|
1116
|
+
task_id=100,
|
|
1117
|
+
include_comments=True,
|
|
1118
|
+
include_history=True,
|
|
1119
|
+
)
|
|
1120
|
+
|
|
1121
|
+
deals_details = await client.deals.get_full_details(
|
|
1122
|
+
deal_id=200,
|
|
1123
|
+
include_comments=True,
|
|
1124
|
+
include_history=True,
|
|
1125
|
+
)
|
|
1126
|
+
|
|
1127
|
+
projects_details = await client.projects.get_full_details(
|
|
1128
|
+
project_id=300,
|
|
1129
|
+
include_comments=True,
|
|
1130
|
+
include_history=True,
|
|
1131
|
+
)
|
|
1132
|
+
```
|
|
1133
|
+
|
|
1134
|
+
## Автоматическая подгрузка связанных сущностей
|
|
1135
|
+
|
|
1136
|
+
Параметр `expand` позволяет автоматически подгружать связанные сущности (сотрудников, контрагентов, отделы) вместо получения только ID.
|
|
1137
|
+
|
|
1138
|
+
### Использование expand в задачах
|
|
1139
|
+
|
|
1140
|
+
```python
|
|
1141
|
+
# Без expand - получаем только базовую информацию
|
|
1142
|
+
tasks = await client.tasks.list(limit=10)
|
|
1143
|
+
for task in tasks:
|
|
1144
|
+
print(task.responsible) # BaseEntity(id=123, contentType='Employee')
|
|
1145
|
+
|
|
1146
|
+
# С expand - автоматически подгружаются сотрудники
|
|
1147
|
+
tasks_full = await client.tasks.list(limit=10, expand=["responsible", "owner"])
|
|
1148
|
+
for task_full in tasks_full:
|
|
1149
|
+
task = task_full.task
|
|
1150
|
+
if task_full.responsible_details:
|
|
1151
|
+
# Доступ к полным данным сотрудника
|
|
1152
|
+
print(task_full.responsible_details.display_name())
|
|
1153
|
+
# Вывод: "Максим Борзов (Генеральный директор)"
|
|
1154
|
+
```
|
|
1155
|
+
|
|
1156
|
+
**Поддерживаемые поля для expand в задачах:**
|
|
1157
|
+
- `responsible` - ответственный сотрудник
|
|
1158
|
+
- `owner` - автор/постановщик задачи
|
|
1159
|
+
|
|
1160
|
+
### Использование expand в сделках
|
|
1161
|
+
|
|
1162
|
+
```python
|
|
1163
|
+
deals_full = await client.deals.list(limit=10, expand=["responsible", "contractor"])
|
|
1164
|
+
|
|
1165
|
+
for deal_full in deals_full:
|
|
1166
|
+
deal = deal_full.deal
|
|
1167
|
+
print(f"Сделка: {deal.name}")
|
|
1168
|
+
|
|
1169
|
+
if deal_full.responsible_details:
|
|
1170
|
+
print(f"Ответственный: {deal_full.responsible_details.display_name()}")
|
|
1171
|
+
|
|
1172
|
+
if deal_full.contractor_details:
|
|
1173
|
+
print(f"Контрагент: {deal_full.contractor_details.display_name()}")
|
|
1174
|
+
|
|
1175
|
+
# Статус сделки с читаемым выводом
|
|
1176
|
+
if deal.state:
|
|
1177
|
+
print(f"Статус: {deal.state}") # Использует __str__ из ProgramState
|
|
1178
|
+
```
|
|
1179
|
+
|
|
1180
|
+
**Поддерживаемые поля для expand в сделках:**
|
|
1181
|
+
- `responsible` - ответственный сотрудник
|
|
1182
|
+
- `contractor` - контрагент
|
|
1183
|
+
|
|
1184
|
+
### Использование expand в проектах
|
|
1185
|
+
|
|
1186
|
+
```python
|
|
1187
|
+
projects_full = await client.projects.list(limit=10, expand=["responsible", "owner"])
|
|
1188
|
+
|
|
1189
|
+
for project_full in projects_full:
|
|
1190
|
+
if project_full.responsible_details:
|
|
1191
|
+
print(f"Ответственный: {project_full.responsible_details.display_name()}")
|
|
1192
|
+
```
|
|
1193
|
+
|
|
1194
|
+
**Поддерживаемые поля для expand в проектах:**
|
|
1195
|
+
- `responsible` - ответственный сотрудник
|
|
1196
|
+
- `owner` - владелец проекта
|
|
1197
|
+
|
|
1198
|
+
### Использование expand в сотрудниках
|
|
1199
|
+
|
|
1200
|
+
```python
|
|
1201
|
+
employees = await client.employees.list(limit=10, expand=["department", "manager"])
|
|
1202
|
+
|
|
1203
|
+
for employee in employees:
|
|
1204
|
+
# Используем helper метод для форматированного вывода
|
|
1205
|
+
print(f"Сотрудник: {employee.display_name()}")
|
|
1206
|
+
|
|
1207
|
+
# Отдел подгружен как полный объект Department
|
|
1208
|
+
if employee.department and hasattr(employee.department, 'name'):
|
|
1209
|
+
print(f"Отдел: {employee.department.name}")
|
|
1210
|
+
|
|
1211
|
+
# Руководитель подгружен как полный объект Employee
|
|
1212
|
+
if employee.manager and hasattr(employee.manager, 'display_name'):
|
|
1213
|
+
print(f"Руководитель: {employee.manager.display_name()}")
|
|
1214
|
+
```
|
|
1215
|
+
|
|
1216
|
+
**Поддерживаемые поля для expand в сотрудниках:**
|
|
1217
|
+
- `department` - отдел сотрудника
|
|
1218
|
+
- `manager` - непосредственный руководитель
|
|
1219
|
+
|
|
1220
|
+
### Helper методы для читаемого вывода
|
|
1221
|
+
|
|
1222
|
+
Модели содержат удобные методы для форматированного вывода:
|
|
1223
|
+
|
|
1224
|
+
```python
|
|
1225
|
+
# Employee
|
|
1226
|
+
employee.full_name() # "Максим Борзов"
|
|
1227
|
+
employee.full_name(include_middle=True) # "Максим Александрович Борзов"
|
|
1228
|
+
employee.display_name() # "Максим Борзов (Генеральный директор)"
|
|
1229
|
+
str(employee) # То же, что display_name()
|
|
1230
|
+
|
|
1231
|
+
# Contractor
|
|
1232
|
+
contractor.display_name() # "ООО Рога и Копыта" или "Contractor#123"
|
|
1233
|
+
str(contractor) # То же, что display_name()
|
|
1234
|
+
|
|
1235
|
+
# Department
|
|
1236
|
+
str(department) # "IT отдел" или "Department#5"
|
|
1237
|
+
|
|
1238
|
+
# ProgramState (статус сделки)
|
|
1239
|
+
str(deal.state) # "Переговоры" или "State#10"
|
|
1240
|
+
```
|
|
1241
|
+
|
|
1242
|
+
### Производительность
|
|
1243
|
+
|
|
1244
|
+
Использование `expand` значительно сокращает количество API запросов:
|
|
1245
|
+
|
|
1246
|
+
```python
|
|
1247
|
+
# БЕЗ expand: 1 запрос на список + N запросов на каждого уникального сотрудника
|
|
1248
|
+
tasks = await client.tasks.list(limit=100) # 1 запрос
|
|
1249
|
+
for task in tasks:
|
|
1250
|
+
if task.responsible:
|
|
1251
|
+
# Нужно загрузить сотрудника отдельно (100+ запросов)
|
|
1252
|
+
employee = await client.employees.get(task.responsible.id)
|
|
1253
|
+
|
|
1254
|
+
# С expand: 1 запрос на список + 1 батч запросов на уникальных сотрудников
|
|
1255
|
+
tasks_full = await client.tasks.list(limit=100, expand=["responsible"])
|
|
1256
|
+
# Всего: 2 запроса (список задач + батч сотрудников)
|
|
1257
|
+
# Повторные сотрудники берутся из кэша!
|
|
1258
|
+
```
|
|
1259
|
+
|
|
1260
|
+
**Пример**:
|
|
1261
|
+
- 100 задач с 5 уникальными ответственными
|
|
1262
|
+
- Без expand: 101 запрос (1 список + 100 запросов на сотрудников)
|
|
1263
|
+
- С expand: 2 запроса (1 список + 1 батч на 5 сотрудников)
|
|
1264
|
+
- **Экономия: 99 запросов (98%)**
|
|
1265
|
+
|
|
1266
|
+
## Известные ограничения API
|
|
1267
|
+
|
|
1268
|
+
Некоторые эндпоинты Megaplan API имеют ограничения или известные проблемы:
|
|
1269
|
+
|
|
1270
|
+
### Комментарии контрагентов
|
|
1271
|
+
|
|
1272
|
+
API возвращает ошибку 500 при попытке получить или создать комментарии для контрагентов. Для отслеживания взаимодействия с контрагентами используйте:
|
|
1273
|
+
- Журнал действий (action history)
|
|
1274
|
+
- Комментарии в связанных сделках
|
|
1275
|
+
- Комментарии в связанных задачах
|
|
1276
|
+
|
|
1277
|
+
```python
|
|
1278
|
+
# Это НЕ работает - вернет 500 ошибку
|
|
1279
|
+
# comments = await client.contractors.get_comments(contractor_id=123)
|
|
1280
|
+
|
|
1281
|
+
# Вместо этого используйте комментарии в сделках контрагента
|
|
1282
|
+
deals = await client.deals.list(
|
|
1283
|
+
base_on={"contentType": "Contractor", "id": 123}
|
|
1284
|
+
)
|
|
1285
|
+
for deal in deals:
|
|
1286
|
+
comments = await client.deals.get_comments(deal.id)
|
|
1287
|
+
```
|
|
1288
|
+
|
|
1289
|
+
### Поиск сотрудников
|
|
1290
|
+
|
|
1291
|
+
Поиск сотрудников по имени или телефону может работать некорректно и возвращать 0 результатов. Для надежного поиска используйте точный email:
|
|
1292
|
+
|
|
1293
|
+
```python
|
|
1294
|
+
# Может не работать
|
|
1295
|
+
employees = await client.employees.list(q="Иван Иванов")
|
|
1296
|
+
|
|
1297
|
+
# Рекомендуется - поиск по точному email
|
|
1298
|
+
employees = await client.employees.list(q="ivan@example.com")
|
|
1299
|
+
|
|
1300
|
+
# Или загрузите всех сотрудников и фильтруйте локально
|
|
1301
|
+
all_employees = []
|
|
1302
|
+
async for emp in client.employees.iterate():
|
|
1303
|
+
if "Иван" in emp.first_name:
|
|
1304
|
+
all_employees.append(emp)
|
|
1305
|
+
```
|
|
1306
|
+
|
|
1307
|
+
## Архитектура
|
|
1308
|
+
|
|
1309
|
+
SDK спроектирован с учетом модульности:
|
|
1310
|
+
|
|
1311
|
+
- Resources (`TasksResource`, `ProjectsResource`, etc.) - Обработка операций API
|
|
1312
|
+
- Models (Pydantic) - Типобезопасные структуры данных
|
|
1313
|
+
- HTTPClient - Низкоуровневые HTTP-операции с retry и авторизацией
|
|
1314
|
+
- AuthManager - Управление OAuth2-токенами
|
|
1315
|
+
|
|
1316
|
+
### Расширение SDK
|
|
1317
|
+
|
|
1318
|
+
Для добавления нового ресурса:
|
|
1319
|
+
|
|
1320
|
+
1. Создайте модель в `src/megaplan_sdk/models/`
|
|
1321
|
+
2. Создайте ресурс в `src/megaplan_sdk/resources/`, наследуя от `BaseResource`
|
|
1322
|
+
3. Добавьте ресурс в `MegaplanClient`:
|
|
1323
|
+
|
|
1324
|
+
```python
|
|
1325
|
+
class MegaplanClient:
|
|
1326
|
+
def __init__(self, ...):
|
|
1327
|
+
# ... существующий код ...
|
|
1328
|
+
self.new_resource = NewResource(self._http)
|
|
1329
|
+
```
|
|
1330
|
+
|
|
1331
|
+
## Требования
|
|
1332
|
+
|
|
1333
|
+
- Python 3.11+
|
|
1334
|
+
- httpx >= 0.25.0
|
|
1335
|
+
- pydantic >= 2.0.0
|
|
1336
|
+
|
|
1337
|
+
## Разработка
|
|
1338
|
+
|
|
1339
|
+
### Установка для разработки
|
|
1340
|
+
|
|
1341
|
+
```bash
|
|
1342
|
+
git clone https://github.com/borzov/megaplan-sdk.git
|
|
1343
|
+
cd megaplan-sdk
|
|
1344
|
+
pip install -e ".[dev]"
|
|
1345
|
+
```
|
|
1346
|
+
|
|
1347
|
+
### Запуск тестов
|
|
1348
|
+
|
|
1349
|
+
```bash
|
|
1350
|
+
pytest
|
|
1351
|
+
```
|
|
1352
|
+
|
|
1353
|
+
С покрытием:
|
|
1354
|
+
|
|
1355
|
+
```bash
|
|
1356
|
+
pytest --cov=megaplan_sdk --cov-report=html
|
|
1357
|
+
```
|
|
1358
|
+
|
|
1359
|
+
### Проверка типов
|
|
1360
|
+
|
|
1361
|
+
```bash
|
|
1362
|
+
mypy megaplan_sdk
|
|
1363
|
+
```
|
|
1364
|
+
|
|
1365
|
+
### Линтинг
|
|
1366
|
+
|
|
1367
|
+
```bash
|
|
1368
|
+
ruff check megaplan_sdk
|
|
1369
|
+
ruff format megaplan_sdk
|
|
1370
|
+
```
|
|
1371
|
+
|
|
1372
|
+
## Лицензия
|
|
1373
|
+
|
|
1374
|
+
MIT License
|
|
1375
|
+
|
|
1376
|
+
## Ссылки
|
|
1377
|
+
|
|
1378
|
+
- [Документация API Мегаплана](https://dev.megaplan.ru/apiv3/index.html)
|
|
1379
|
+
- [Официальный PHP SDK](https://github.com/megaplan/megaplansdk)
|
|
1380
|
+
|
|
1381
|
+
## Вклад в проект
|
|
1382
|
+
|
|
1383
|
+
Вклад приветствуется! Пожалуйста, не стесняйтесь отправлять Pull Request.
|