megaplan-sdk 0.1.0__tar.gz → 0.2.1__tar.gz

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.
Files changed (49) hide show
  1. {megaplan_sdk-0.1.0/src/megaplan_sdk.egg-info → megaplan_sdk-0.2.1}/PKG-INFO +894 -488
  2. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/README.md +893 -487
  3. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/pyproject.toml +1 -1
  4. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/__init__.py +38 -0
  5. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/auth.py +2 -2
  6. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/cache.py +35 -21
  7. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/client.py +6 -0
  8. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/constants.py +1 -0
  9. megaplan_sdk-0.2.1/src/megaplan_sdk/filter_builder.py +621 -0
  10. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/http_client.py +15 -13
  11. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/__init__.py +6 -0
  12. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/comment.py +0 -15
  13. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/common.py +1 -15
  14. megaplan_sdk-0.2.1/src/megaplan_sdk/models/filter.py +82 -0
  15. megaplan_sdk-0.2.1/src/megaplan_sdk/models/group.py +40 -0
  16. megaplan_sdk-0.2.1/src/megaplan_sdk/models/milestone.py +81 -0
  17. megaplan_sdk-0.2.1/src/megaplan_sdk/models/participant.py +74 -0
  18. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/project.py +18 -2
  19. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/task.py +18 -2
  20. megaplan_sdk-0.2.1/src/megaplan_sdk/resources/__init__.py +25 -0
  21. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/base.py +233 -17
  22. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/comments.py +1 -1
  23. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/contractors.py +59 -8
  24. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/deals.py +179 -9
  25. megaplan_sdk-0.2.1/src/megaplan_sdk/resources/filters.py +444 -0
  26. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/projects.py +199 -22
  27. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/tasks.py +367 -31
  28. megaplan_sdk-0.2.1/src/megaplan_sdk/types.py +98 -0
  29. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1/src/megaplan_sdk.egg-info}/PKG-INFO +894 -488
  30. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk.egg-info/SOURCES.txt +6 -0
  31. megaplan_sdk-0.1.0/src/megaplan_sdk/resources/__init__.py +0 -15
  32. megaplan_sdk-0.1.0/src/megaplan_sdk/types.py +0 -56
  33. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/LICENSE +0 -0
  34. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/setup.cfg +0 -0
  35. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/exceptions.py +0 -0
  36. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/helpers.py +0 -0
  37. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/logging_config.py +0 -0
  38. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/base.py +0 -0
  39. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/contractor.py +0 -0
  40. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/deal.py +0 -0
  41. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/department.py +0 -0
  42. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/employee.py +0 -0
  43. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/auth.py +0 -0
  44. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/departments.py +0 -0
  45. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/employees.py +0 -0
  46. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/full_details.py +0 -0
  47. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk.egg-info/dependency_links.txt +0 -0
  48. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk.egg-info/requires.txt +0 -0
  49. {megaplan_sdk-0.1.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: megaplan-sdk
3
- Version: 0.1.0
3
+ Version: 0.2.1
4
4
  Summary: Professional Python SDK for Megaplan API v3
5
5
  Author-email: Maxim Borzov <max@borzov.com>
6
6
  License-Expression: MIT
@@ -54,6 +54,35 @@ Dynamic: license-file
54
54
 
55
55
  Библиотека полностью асинхронная, что позволяет эффективно работать с большими объемами данных и выполнять параллельные запросы. Встроенная логика повторных попыток при временных сбоях сервера делает интеграцию более надежной. Модульная архитектура позволяет легко расширять функциональность и добавлять поддержку новых модулей API.
56
56
 
57
+ ## Содержание
58
+
59
+ ### Основы
60
+ - [Быстрый старт](#быстрый-старт)
61
+ - [Авторизация](#авторизация)
62
+ - [Helper-функции](#helper-функции)
63
+ - [Обработка ошибок](#обработка-ошибок)
64
+
65
+ ### Работа с сущностями
66
+ - [Общие паттерны](#общие-паттерны-работы-с-сущностями)
67
+ - [Задачи](#работа-с-задачами)
68
+ - [Проекты](#работа-с-проектами)
69
+ - [Сделки](#работа-со-сделками)
70
+
71
+ ### Продвинутые возможности
72
+ - [Кэширование сущностей](#кэширование-сущностей)
73
+ - [Глобальные дефолтные лимиты](#глобальные-дефолтные-лимиты)
74
+ - [Автоматическая подгрузка связанных сущностей](#автоматическая-подгрузка-связанных-сущностей)
75
+ - [Работа с фильтрами](#работа-с-фильтрами)
76
+ - [Настройка HTTP-клиента](#настройка-http-клиента)
77
+ - [Работа через прокси](#работа-через-прокси)
78
+ - [Ручное управление токенами](#ручное-управление-токенами)
79
+
80
+ ### Справочная информация
81
+ - [Известные ограничения API](#известные-ограничения-api)
82
+ - [Архитектура](#архитектура)
83
+ - [Требования](#требования)
84
+ - [Разработка](#разработка)
85
+
57
86
  ## Возможности
58
87
 
59
88
  - Полный CRUD для задач, проектов и сделок
@@ -62,6 +91,12 @@ Dynamic: license-file
62
91
  - Типобезопасность с Pydantic-моделями и полной типизацией
63
92
  - Асинхронность — поддержка async/await во всех операциях
64
93
  - Автоматические повторы при ошибках сервера (5xx)
94
+ - Кэширование сущностей с LRU и TTL для оптимизации запросов
95
+ - FilterBuilder для создания фильтров с fluent API
96
+ - Параметр `expand` для автоматической подгрузки связанных сущностей
97
+ - Метод `iterate()` для автоматической пагинации больших списков
98
+ - Helper-функции для создания BaseEntity объектов
99
+ - Глобальные дефолтные лимиты для комментариев и истории
65
100
  - Модульная архитектура для легкого расширения
66
101
  - Комплексные тесты с покрытием 80%+
67
102
 
@@ -163,58 +198,311 @@ deal_ref = make_deal_entity(101)
163
198
  contractor_ref = make_contractor_entity(202)
164
199
  ```
165
200
 
166
- ## Работа с задачами
201
+ ## Обработка ошибок
167
202
 
168
- ### Получение списка задач
203
+ SDK предоставляет специфичные типы исключений для различных сценариев ошибок:
169
204
 
170
205
  ```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: Отдавать только перечисленные поля
206
+ from megaplan_sdk import (
207
+ AuthenticationError, # 401 - Ошибка аутентификации
208
+ AuthorizationError, # 403 - Ошибка авторизации (нет прав)
209
+ NotFoundError, # 404 - Ресурс не найден
210
+ ValidationError, # 422 - Ошибка валидации запроса
211
+ RateLimitError, # 429 - Превышен лимит запросов
212
+ ServerError # 5xx - Ошибка сервера
181
213
  )
182
- # Возвращает: list[Task] - список объектов Task
214
+
215
+ try:
216
+ task = await client.tasks.get(task_id=999)
217
+ except NotFoundError:
218
+ print("Задача не найдена")
219
+ except AuthenticationError:
220
+ print("Ошибка аутентификации")
221
+ except ValidationError as e:
222
+ print(f"Ошибки валидации: {e.errors}")
223
+ # e.errors содержит список ошибок из API
183
224
  ```
184
225
 
185
- **Примеры использования:**
226
+ ## Общие паттерны работы с сущностями
227
+
228
+ Большинство сущностей (задачи, проекты, сделки) поддерживают одинаковые операции CRUD и паттерны работы. В этом разделе описаны общие методы, которые применяются ко всем типам сущностей.
229
+
230
+ ### Базовые операции CRUD
231
+
232
+ Все ресурсы поддерживают стандартные операции:
233
+
234
+ #### Получение списка (`list`)
235
+
236
+ ```python
237
+ # Общий формат для всех ресурсов
238
+ entities = await client.{resource}.list(
239
+ limit=None, # int: Количество элементов на странице
240
+ page_after=None, # dict: Загрузить страницу, начиная с этой сущности
241
+ page_before=None, # dict: Загрузить страницу строго до этой сущности
242
+ page_with=None, # dict: Загрузить страницу с наличием этой сущности
243
+ fields=None, # any: Набор дополнительных полей
244
+ sort_by=None, # list[dict]: Массив полей сортировки
245
+ only_requested_fields=None # bool: Отдавать только перечисленные поля
246
+ )
247
+ ```
186
248
 
249
+ **Примеры:**
187
250
  ```python
188
251
  # Получить все задачи
189
252
  tasks = await client.tasks.list()
190
253
 
191
- # С фильтром по статусам
192
- tasks = await client.tasks.list(
193
- statuses=["assigned", "in_progress"],
194
- limit=50
254
+ # Получить проекты с лимитом
255
+ projects = await client.projects.list(limit=50)
256
+
257
+ # Получить сделки с пагинацией
258
+ deals = await client.deals.list(limit=100, page_after={"contentType": "Deal", "id": 100})
259
+ ```
260
+
261
+ #### Получение по ID (`get`)
262
+
263
+ ```python
264
+ entity = await client.{resource}.get({resource}_id=42)
265
+ # Возвращает: объект сущности со всеми полями
266
+ ```
267
+
268
+ **Примеры:**
269
+ ```python
270
+ task = await client.tasks.get(task_id=42)
271
+ project = await client.projects.get(project_id=5)
272
+ deal = await client.deals.get(deal_id=200)
273
+ ```
274
+
275
+ #### Создание (`create`)
276
+
277
+ ```python
278
+ entity = await client.{resource}.create({resource}_data={
279
+ "name": "Название", # Обязательное поле
280
+ # ... другие поля
281
+ })
282
+ # Возвращает: созданная сущность
283
+ ```
284
+
285
+ **Примеры:**
286
+ ```python
287
+ # Простое создание задачи
288
+ task = await client.tasks.create({"name": "Новая задача"})
289
+
290
+ # Создание проекта
291
+ project = await client.projects.create({"name": "Новый проект"})
292
+
293
+ # Создание сделки (требует program)
294
+ deal = await client.deals.create({
295
+ "name": "Новая сделка",
296
+ "program": {"contentType": "Program", "id": 10}
297
+ })
298
+ ```
299
+
300
+ #### Обновление (`update`)
301
+
302
+ ```python
303
+ entity = await client.{resource}.update(
304
+ {resource}_id=42,
305
+ {resource}_data={
306
+ "name": "Обновленное название",
307
+ # ... другие поля для обновления
308
+ }
195
309
  )
310
+ # Возвращает: обновленная сущность
311
+ ```
196
312
 
197
- # С фильтром по ID
198
- tasks = await client.tasks.list(filter=123)
313
+ **Примеры:**
314
+ ```python
315
+ task = await client.tasks.update(task_id=42, task_data={"status": "completed"})
316
+ project = await client.projects.update(project_id=5, project_data={"name": "Новое название"})
317
+ deal = await client.deals.update(deal_id=200, deal_data={"sum_base": 60000.0})
318
+ ```
199
319
 
200
- # С фильтром по конфигурации
201
- tasks = await client.tasks.list(filter={"status": "active"})
320
+ #### Удаление (`delete`)
202
321
 
203
- # Итерация по всем задачам с автоматической пагинацией
322
+ ```python
323
+ await client.{resource}.delete({resource}_id=42)
324
+ # Возвращает: None
325
+ ```
326
+
327
+ **Примеры:**
328
+ ```python
329
+ await client.tasks.delete(task_id=42)
330
+ await client.projects.delete(project_id=5)
331
+ await client.deals.delete(deal_id=200)
332
+ ```
333
+
334
+ ### Пагинация
335
+
336
+ SDK поддерживает несколько способов работы с большими списками:
337
+
338
+ #### Ручная пагинация
339
+
340
+ ```python
341
+ # Пагинация "после" определенной сущности
342
+ entities = await client.tasks.list(
343
+ limit=50,
344
+ page_after={"contentType": "Task", "id": 100}
345
+ )
346
+
347
+ # Пагинация "до" определенной сущности
348
+ entities = await client.tasks.list(
349
+ limit=50,
350
+ page_before={"contentType": "Task", "id": 200}
351
+ )
352
+
353
+ # Пагинация "с" определенной сущностью
354
+ entities = await client.tasks.list(
355
+ limit=50,
356
+ page_with={"contentType": "Task", "id": 150}
357
+ )
358
+ ```
359
+
360
+ #### Автоматическая пагинация с `iterate()`
361
+
362
+ Метод `iterate()` автоматически обрабатывает пагинацию и возвращает все элементы:
363
+
364
+ ```python
365
+ # Итерация по всем задачам
204
366
  async for task in client.tasks.iterate(limit=100):
205
367
  print(task.name)
368
+
369
+ # Итерация по всем проектам
370
+ async for project in client.projects.iterate(limit=50):
371
+ print(project.name)
372
+
373
+ # Итерация по всем сделкам
374
+ async for deal in client.deals.iterate(limit=200):
375
+ print(deal.name)
206
376
  ```
207
377
 
208
- ### Получение задачи по ID
378
+ ### Получение полной информации (`get_full_details`)
379
+
380
+ Метод `get_full_details()` позволяет получить сущность со всеми связанными данными за один вызов. Все запросы выполняются параллельно для максимальной производительности.
209
381
 
382
+ **Общий формат:**
210
383
  ```python
211
- task = await client.tasks.get(task_id=42)
212
- # Параметры:
213
- # task_id: int - Идентификатор задачи
214
- # Возвращает: Task - объект задачи со всеми полями
384
+ details = await client.{resource}.get_full_details(
385
+ {resource}_id=42,
386
+ include_comments=True, # Загрузить комментарии
387
+ include_history=True, # Загрузить историю изменений
388
+ comments_limit=50, # Лимит комментариев (опционально)
389
+ history_limit=100 # Лимит записей истории (опционально)
390
+ # ... другие специфичные параметры для каждого типа
391
+ )
392
+ ```
393
+
394
+ **Примеры для разных типов:**
395
+
396
+ ```python
397
+ # Задача со всеми данными
398
+ task_details = await client.tasks.get_full_details(
399
+ task_id=42,
400
+ include_comments=True,
401
+ include_sub_tasks=True,
402
+ include_responsible_details=True
403
+ )
404
+
405
+ # Проект со всеми данными
406
+ project_details = await client.projects.get_full_details(
407
+ project_id=5,
408
+ include_deals=True,
409
+ include_issues=True,
410
+ include_comments=True
411
+ )
412
+
413
+ # Сделка со всеми данными
414
+ deal_details = await client.deals.get_full_details(
415
+ deal_id=200,
416
+ include_comments=True,
417
+ include_status_history=True,
418
+ include_contractor_details=True
419
+ )
420
+ ```
421
+
422
+ **Доступ к данным:**
423
+ ```python
424
+ # Основная сущность
425
+ print(details.task.name) # для задач
426
+ print(details.project.name) # для проектов
427
+ print(details.deal.name) # для сделок
428
+
429
+ # Связанные данные
430
+ if details.comments:
431
+ for comment in details.comments:
432
+ print(comment.text)
433
+
434
+ if details.history:
435
+ print(f"Записей в истории: {len(details.history)}")
436
+ ```
437
+
438
+ Подробнее о специфичных параметрах для каждого типа сущностей см. в соответствующих разделах:
439
+ - [Задачи](#работа-с-задачами)
440
+ - [Проекты](#работа-с-проектами)
441
+ - [Сделки](#работа-со-сделками)
442
+
443
+ ## Работа с задачами
444
+
445
+ > **Примечание:** Базовые операции CRUD (list, get, create, update, delete) и пагинация описаны в разделе [Общие паттерны работы с сущностями](#общие-паттерны-работы-с-сущностями).
446
+
447
+ ### Специфичные параметры для задач
448
+
449
+ #### Получение списка задач с фильтрацией
450
+
451
+ Метод `list()` поддерживает дополнительные параметры для задач:
452
+
453
+ ```python
454
+ tasks = await client.tasks.list(
455
+ filter=None, # TaskFilter: ID фильтра (int/str) или FilterBuilder объект
456
+ statuses=None, # list[str]: Статусы задач для фильтрации
457
+ # ... остальные параметры из общих паттернов
458
+ )
215
459
  ```
216
460
 
217
- **Поля объекта Task:**
461
+ **Примеры использования фильтров:**
462
+
463
+ ```python
464
+ # С фильтром по статусам
465
+ tasks = await client.tasks.list(
466
+ statuses=["assigned", "in_progress"],
467
+ limit=50
468
+ )
469
+
470
+ # С фильтром по ID (int или str)
471
+ tasks = await client.tasks.list(filter=123)
472
+ tasks = await client.tasks.list(filter="incoming")
473
+
474
+ # С FilterBuilder для текстового поиска (рекомендуется)
475
+ from megaplan_sdk import TaskFilterBuilder
476
+
477
+ # Простой поиск по названию
478
+ filter_obj = TaskFilterBuilder().field("name").contains("договор").build()
479
+ tasks = await client.tasks.list(filter=filter_obj)
480
+
481
+ # Несколько условий с AND
482
+ filter_obj = (
483
+ TaskFilterBuilder()
484
+ .field("name").contains("договор")
485
+ .and_()
486
+ .field("name").starts_with("Важный")
487
+ .build()
488
+ )
489
+ tasks = await client.tasks.list(filter=filter_obj)
490
+
491
+ # Условия с OR
492
+ filter_obj = (
493
+ TaskFilterBuilder()
494
+ .field("name").contains("договор")
495
+ .or_()
496
+ .field("name").contains("соглашение")
497
+ .build()
498
+ )
499
+ tasks = await client.tasks.list(filter=filter_obj)
500
+ ```
501
+
502
+ Подробнее о работе с фильтрами см. раздел [Работа с фильтрами](#работа-с-фильтрами).
503
+
504
+ ### Поля модели Task
505
+
218
506
  - `id: int` - Идентификатор задачи
219
507
  - `name: str` - Название задачи
220
508
  - `description: str` - Описание
@@ -232,15 +520,11 @@ task = await client.tasks.get(task_id=42)
232
520
  - `created_at: str` - Дата создания
233
521
  - `updated_at: str` - Дата обновления
234
522
 
235
- ### Создание задачи
523
+ ### Упрощенные методы создания
236
524
 
237
- #### Упрощенное создание (рекомендуется)
525
+ Помимо стандартного `create()`, задачи поддерживают упрощенные методы:
238
526
 
239
527
  ```python
240
- # Простое создание задачи с автоматическим заполнением обязательных полей
241
- # Автоматически устанавливает isUrgent=False, isTemplate=False
242
- task = await client.tasks.create({"name": "Новая задача"})
243
-
244
528
  # Создание задачи с текущим пользователем как ответственным
245
529
  task = await client.tasks.create_simple(
246
530
  "Новая задача",
@@ -248,7 +532,6 @@ task = await client.tasks.create_simple(
248
532
  )
249
533
 
250
534
  # Создание задачи с указанным ответственным
251
- from megaplan_sdk import make_employee_entity
252
535
  task = await client.tasks.create_simple(
253
536
  "Новая задача",
254
537
  responsible_id=123
@@ -262,54 +545,7 @@ task = await client.tasks.create_in_project(
262
545
  )
263
546
  ```
264
547
 
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
- ```
548
+ **Примечание:** Стандартный метод `create()` также поддерживается. При создании задачи автоматически устанавливаются `isUrgent=False` и `isTemplate=False`, если они не указаны явно.
313
549
 
314
550
  ### Получение подзадач
315
551
 
@@ -335,6 +571,53 @@ actual_subtasks = await client.tasks.get_actual_sub_tasks(
335
571
  # Возвращает: list[Task] - список актуальных подзадач
336
572
  ```
337
573
 
574
+ ### Получение доступных родителей
575
+
576
+ Методы для получения доступных надзадач и надпроектов (для выбора родителя при создании или перемещении задачи):
577
+
578
+ ```python
579
+ # Глобальный поиск доступных родителей для новой задачи
580
+ # Возвращает список Task и Project объектов
581
+ parents = await client.tasks.get_available_parents(
582
+ is_template=False, # bool: Фильтр по шаблонам
583
+ limit=10, # int: Количество элементов
584
+ )
585
+ for parent in parents:
586
+ print(f"{type(parent).__name__}: {parent.name}") # "Task: ..." или "Project: ..."
587
+
588
+ # Доступные родители для существующей задачи
589
+ # Исключает саму задачу и её потомков
590
+ parents = await client.tasks.get_available_parents_for(
591
+ task_id=123,
592
+ is_template=False,
593
+ limit=10,
594
+ )
595
+ ```
596
+
597
+ **Примечание:** Методы возвращают смешанный список объектов `Task` и `Project`, так как задача может быть вложена как в другую задачу, так и в проект.
598
+
599
+ ### Получение всех участников задачи
600
+
601
+ Метод `get_all_participants()` возвращает полный список участников задачи (ответственный, соисполнители, аудиторы, владелец) в одном запросе:
602
+
603
+ ```python
604
+ participants = await client.tasks.get_all_participants(
605
+ task_id=123,
606
+ limit=None, # int: Количество элементов
607
+ # ... стандартные параметры пагинации
608
+ )
609
+ # Возвращает: list[Employee | ContractorHuman | Group]
610
+
611
+ for participant in participants:
612
+ if hasattr(participant, 'display_name'):
613
+ print(participant.display_name())
614
+ ```
615
+
616
+ **Типы участников:**
617
+ - `Employee` — сотрудник организации
618
+ - `ContractorHuman` — контрагент-физлицо
619
+ - `Group` — группа участников (например, отдел)
620
+
338
621
  ### Получение задач на уровне дерева
339
622
 
340
623
  ```python
@@ -353,48 +636,23 @@ tasks = await client.tasks.tree_level(
353
636
 
354
637
  ### Получение полной информации о задаче
355
638
 
356
- Метод `get_full_details()` позволяет получить задачу со всеми связанными данными за один вызов. Все запросы выполняются параллельно для максимальной производительности.
639
+ Метод `get_full_details()` для задач поддерживает следующие специфичные параметры:
357
640
 
358
641
  ```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}")
642
+ details = await client.tasks.get_full_details(
643
+ task_id=42,
644
+ include_sub_tasks=True, # Загрузить подзадачи
645
+ include_actual_sub_tasks=True, # Загрузить актуальные подзадачи
646
+ include_comments=True, # Загрузить комментарии
647
+ include_history=True, # Загрузить историю изменений
648
+ include_auditors=True, # Загрузить список аудиторов
649
+ include_executors=True, # Загрузить соисполнителей
650
+ include_milestones=True, # Загрузить вехи
651
+ include_responsible_details=True, # Загрузить полные данные ответственного
652
+ include_owner_details=True, # Загрузить полные данные постановщика
653
+ comments_limit=50, # Лимит комментариев (опционально)
654
+ history_limit=100 # Лимит записей истории (опционально)
655
+ )
398
656
  ```
399
657
 
400
658
  **Поля объекта TaskFullDetails:**
@@ -405,64 +663,105 @@ async with MegaplanClient(...) as client:
405
663
  - `history: list[dict] | None` - История изменений
406
664
  - `auditors: list[dict] | None` - Аудиторы
407
665
  - `executors: list[dict] | None` - Соисполнители
408
- - `milestones: list[dict] | None` - Вехи
666
+ - `milestones: list[Milestone] | None` - Вехи
409
667
  - `responsible_details: Employee | None` - Полные данные ответственного
410
668
  - `owner_details: Employee | None` - Полные данные постановщика
411
669
 
412
- **Примеры использования:**
670
+ > **Примечание:** Общее описание метода `get_full_details()` и примеры использования см. в разделе [Общие паттерны работы с сущностями](#общие-паттерны-работы-с-сущностями).
671
+
672
+ ### Работа с вехами (Milestones)
673
+
674
+ Вехи можно получать и создавать для задач и проектов.
675
+
676
+ #### Получение вех
413
677
 
414
678
  ```python
415
- # Минимальный вызов - только основная задача
416
- details = await client.tasks.get_full_details(task_id=42)
679
+ # Получить вехи задачи
680
+ milestones = await client.tasks.get_milestones(
681
+ task_id=123,
682
+ limit=50 # Опционально
683
+ )
417
684
 
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
685
+ # Получить вехи проекта
686
+ milestones = await client.projects.get_milestones(
687
+ project_id=456,
688
+ limit=50 # Опционально
424
689
  )
425
690
 
426
- # Полная информация для отчета
691
+ # Вехи также доступны через get_full_details()
427
692
  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
693
+ task_id=123,
694
+ include_milestones=True
436
695
  )
696
+ if details.milestones:
697
+ for milestone in details.milestones:
698
+ print(f"{milestone.name}: {milestone.type}")
437
699
  ```
438
700
 
439
- ## Работа с проектами
440
-
441
- ### Получение списка проектов
701
+ #### Создание вехи
442
702
 
443
703
  ```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: Отдавать только перечисленные поля
704
+ from megaplan_sdk.models.milestone import Milestone
705
+
706
+ # Создать веху для задачи
707
+ milestone = await client.tasks.add_milestone(
708
+ task_id=123,
709
+ milestone_data={
710
+ "name": "Release 1.0",
711
+ "description": "Release milestone description", # Обязательное поле
712
+ "type": "report", # Обязательное: "report", "reminder", или "note"
713
+ "date": "2026-02-01T10:00:00Z" # Обязательное: ISO 8601 формат
714
+ }
452
715
  )
453
- # Возвращает: list[Project] - список объектов Project
454
- ```
455
716
 
456
- ### Получение проекта по ID
717
+ # Или использовать модель Milestone
718
+ milestone = await client.tasks.add_milestone(
719
+ task_id=123,
720
+ milestone_data=Milestone(
721
+ name="Release 1.0",
722
+ description="Release milestone description",
723
+ type="report",
724
+ date="2026-02-01T10:00:00Z"
725
+ )
726
+ )
457
727
 
458
- ```python
459
- project = await client.projects.get(project_id=5)
460
- # Параметры:
461
- # project_id: int - Идентификатор проекта
462
- # Возвращает: Project - объект проекта со всеми полями
728
+ # Создать веху для проекта
729
+ milestone = await client.projects.add_milestone(
730
+ project_id=456,
731
+ milestone_data={
732
+ "description": "Phase 1 completion",
733
+ "type": "reminder",
734
+ "date": "2026-03-15T14:00:00Z"
735
+ }
736
+ )
463
737
  ```
464
738
 
465
- **Поля объекта Project:**
739
+ **Обязательные поля при создании вехи:**
740
+ - `description: str` - Описание вехи
741
+ - `type: str` - Тип вехи: `"report"`, `"reminder"`, или `"note"`
742
+ - `date: str | DateTime | dict` - Дата и время вехи (ISO 8601 строка или объект DateTime)
743
+
744
+ **Поля модели Milestone:**
745
+ - `id: int` - Идентификатор вехи
746
+ - `name: str | None` - Название вехи
747
+ - `description: str | None` - Описание
748
+ - `completed: bool | None` - Признак завершенности
749
+ - `type: str | None` - Тип вехи
750
+ - `date: str | DateTime | dict | None` - Дата и время
751
+ - `owner: BaseEntity | None` - Создатель (Employee)
752
+ - `responsible: BaseEntity | None` - Ответственный (Employee)
753
+ - `task: BaseEntity | None` - Связанная задача
754
+ - `project: BaseEntity | None` - Связанный проект
755
+
756
+ **Примечание:** Метод `get_milestones()` может вернуть пустой список для некоторых задач/проектов из-за ограничений API (ошибка 500). Это обрабатывается автоматически.
757
+
758
+ ## Работа с проектами
759
+
760
+ > **Примечание:** Базовые операции CRUD (list, get, create, update, delete) описаны в разделе [Общие паттерны работы с сущностями](#общие-паттерны-работы-с-сущностями).
761
+
762
+ **Важно:** Проекты не поддерживают фильтрацию через API (параметр `filter` недоступен).
763
+
764
+ ### Поля модели Project
466
765
  - `id: int` - Идентификатор проекта
467
766
  - `name: str` - Название проекта
468
767
  - `description: str` - Описание
@@ -479,15 +778,11 @@ project = await client.projects.get(project_id=5)
479
778
  - `created_at: str` - Дата создания
480
779
  - `updated_at: str` - Дата обновления
481
780
 
482
- ### Создание проекта
781
+ ### Упрощенные методы создания
483
782
 
484
- #### Упрощенное создание (рекомендуется)
783
+ Помимо стандартного `create()`, проекты поддерживают упрощенный метод:
485
784
 
486
785
  ```python
487
- # Простое создание проекта с автоматическим заполнением обязательных полей
488
- # Автоматически устанавливает isTemplate=False
489
- project = await client.projects.create({"name": "Новый проект"})
490
-
491
786
  # Создание проекта с текущим пользователем как владельцем и ответственным
492
787
  project = await client.projects.create_simple(
493
788
  "Новый проект",
@@ -502,44 +797,7 @@ project = await client.projects.create_simple(
502
797
  )
503
798
  ```
504
799
 
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
- ```
800
+ **Примечание:** При создании проекта автоматически устанавливается `isTemplate=False`, если не указано явно.
543
801
 
544
802
  ### Получение сделок проекта
545
803
 
@@ -580,54 +838,65 @@ actual_issues = await client.projects.get_actual_issues(
580
838
  # Возвращает: list[Task] - список актуальных задач проекта
581
839
  ```
582
840
 
583
- ### Получение полной информации о проекте
841
+ ### Получение доступных родителей
584
842
 
585
- Метод `get_full_details()` позволяет получить проект со всеми связанными данными за один вызов. Все запросы выполняются параллельно для максимальной производительности.
843
+ Методы для получения доступных родительских проектов (для выбора родителя при создании или перемещении проекта):
586
844
 
587
845
  ```python
588
- from megaplan_sdk import MegaplanClient
846
+ # Глобальный поиск доступных родительских проектов
847
+ parents = await client.projects.get_available_parents(
848
+ is_template=False, # bool: Фильтр по шаблонам
849
+ limit=10, # int: Количество элементов
850
+ )
851
+ for parent in parents:
852
+ print(f"Project: {parent.name}")
589
853
 
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
- )
854
+ # Доступные родители для существующего проекта
855
+ # Исключает сам проект и его потомков
856
+ parents = await client.projects.get_available_parents_for(
857
+ project_id=456,
858
+ is_template=False,
859
+ limit=10,
860
+ )
861
+ ```
607
862
 
608
- # Доступ к основным данным проекта
609
- print(f"Проект: {details.project.name}")
610
- print(f"Статус: {details.project.status}")
863
+ **Примечание:** В отличие от задач, проекты могут быть вложены только в другие проекты, поэтому возвращается список объектов `Project`.
611
864
 
612
- # Доступ к связанным данным
613
- if details.deals:
614
- print(f"Сделок: {len(details.deals)}")
615
- for deal in details.deals:
616
- print(f" - {deal.name}")
865
+ ### Получение всех участников проекта
617
866
 
618
- if details.issues:
619
- print(f"Задач: {len(details.issues)}")
620
- for task in details.issues:
621
- print(f" - {task.name}")
867
+ Метод `get_all_participants()` возвращает полный список участников проекта в одном запросе:
622
868
 
623
- if details.comments:
624
- print(f"Комментариев: {len(details.comments)}")
869
+ ```python
870
+ participants = await client.projects.get_all_participants(
871
+ project_id=123,
872
+ limit=None, # int: Количество элементов
873
+ )
874
+ # Возвращает: list[Employee | ContractorHuman | Group]
625
875
 
626
- if details.responsible_details:
627
- print(f"Ответственный: {details.responsible_details.first_name} {details.responsible_details.last_name}")
876
+ for participant in participants:
877
+ print(f"{type(participant).__name__}: {participant.display_name()}")
878
+ ```
628
879
 
629
- if details.owner_details:
630
- print(f"Владелец: {details.owner_details.first_name} {details.owner_details.last_name}")
880
+ ### Получение полной информации о проекте
881
+
882
+ Метод `get_full_details()` для проектов поддерживает следующие специфичные параметры:
883
+
884
+ ```python
885
+ details = await client.projects.get_full_details(
886
+ project_id=5,
887
+ include_deals=True, # Загрузить связанные сделки
888
+ include_issues=True, # Загрузить задачи проекта
889
+ include_actual_issues=True, # Загрузить актуальные задачи
890
+ include_comments=True, # Загрузить комментарии
891
+ include_history=True, # Загрузить историю изменений
892
+ include_auditors=True, # Загрузить список аудиторов
893
+ include_executors=True, # Загрузить соисполнителей
894
+ include_milestones=True, # Загрузить вехи
895
+ include_responsible_details=True, # Загрузить полные данные ответственного
896
+ include_owner_details=True, # Загрузить полные данные владельца
897
+ comments_limit=50, # Лимит комментариев (опционально)
898
+ history_limit=100 # Лимит записей истории (опционально)
899
+ )
631
900
  ```
632
901
 
633
902
  **Поля объекта ProjectFullDetails:**
@@ -639,66 +908,63 @@ async with MegaplanClient(...) as client:
639
908
  - `history: list[dict] | None` - История изменений
640
909
  - `auditors: list[dict] | None` - Аудиторы
641
910
  - `executors: list[dict] | None` - Соисполнители
642
- - `milestones: list[dict] | None` - Вехи
911
+ - `milestones: list[Milestone] | None` - Вехи
643
912
  - `responsible_details: Employee | None` - Полные данные ответственного
644
913
  - `owner_details: Employee | None` - Полные данные владельца
645
914
 
646
- **Примеры использования:**
647
-
648
- ```python
649
- # Минимальный вызов - только основной проект
650
- details = await client.projects.get_full_details(project_id=5)
915
+ > **Примечание:** Общее описание метода `get_full_details()` и примеры использования см. в разделе [Общие паттерны работы с сущностями](#общие-паттерны-работы-с-сущностями).
651
916
 
652
- # Проект со сделками и задачами
653
- details = await client.projects.get_full_details(
654
- project_id=5,
655
- include_deals=True,
656
- include_issues=True
657
- )
917
+ ### Работа с вехами (Milestones)
658
918
 
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
- ```
919
+ Вехи для проектов работают аналогично вехам для задач. См. раздел [Работа с вехами](#работа-с-вехами-milestones) в разделе "Работа с задачами" для подробностей.
670
920
 
671
921
  ## Работа со сделками
672
922
 
673
- ### Получение списка сделок
923
+ > **Примечание:** Базовые операции CRUD (list, get, create, update, delete) описаны в разделе [Общие паттерны работы с сущностями](#общие-паттерны-работы-с-сущностями).
924
+
925
+ ### Специфичные параметры для сделок
926
+
927
+ #### Получение списка сделок с фильтрацией
928
+
929
+ Метод `list()` поддерживает дополнительные параметры для сделок:
674
930
 
675
931
  ```python
676
932
  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: Только запрошенные поля
933
+ filter=None, # TradeFilter: ID фильтра (int/str) или FilterBuilder объект
934
+ status=None, # ProgramState: Статус программы для фильтрации
935
+ base_on=None, # BaseEntity: Базовая сущность для фильтрации
936
+ # ... остальные параметры из общих паттернов
688
937
  )
689
- # Возвращает: list[Deal] - список объектов Deal
690
938
  ```
691
939
 
692
- ### Получение сделки по ID
940
+ **Примеры использования фильтров:**
693
941
 
694
942
  ```python
695
- deal = await client.deals.get(deal_id=200)
696
- # Параметры:
697
- # deal_id: int - Идентификатор сделки
698
- # Возвращает: Deal - объект сделки со всеми полями
943
+ # С фильтром по ID
944
+ deals = await client.deals.list(filter=123)
945
+ deals = await client.deals.list(filter="active")
946
+
947
+ # С FilterBuilder для текстового поиска (рекомендуется)
948
+ from megaplan_sdk import TradeFilterBuilder
949
+
950
+ # Простой поиск по названию
951
+ filter_obj = TradeFilterBuilder().field("name").contains("Leader").build()
952
+ deals = await client.deals.list(filter=filter_obj)
953
+
954
+ # Несколько условий
955
+ filter_obj = (
956
+ TradeFilterBuilder()
957
+ .field("name").contains("Leader")
958
+ .and_()
959
+ .field("name").starts_with("Важная")
960
+ .build()
961
+ )
962
+ deals = await client.deals.list(filter=filter_obj)
699
963
  ```
700
964
 
701
- **Поля объекта Deal:**
965
+ Подробнее о работе с фильтрами см. раздел [Работа с фильтрами](#работа-с-фильтрами).
966
+
967
+ ### Поля модели Deal
702
968
  - `id: int` - Идентификатор сделки
703
969
  - `name: str` - Название сделки
704
970
  - `program: BaseEntity` - Программа (схема сделки)
@@ -706,61 +972,19 @@ deal = await client.deals.get(deal_id=200)
706
972
  - `contractor: BaseEntity` - Контрагент (ContractorCompany/ContractorHuman)
707
973
  - `responsible: BaseEntity` - Ответственный (Employee)
708
974
  - `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
- ```
975
+ - `currency: BaseEntity` - Валюта
976
+ - `deadline: str` - Срок
977
+ - `description: str` - Описание
978
+ - `tags: list[BaseEntity]` - Теги
979
+ - `attaches: list[BaseEntity]` - Вложения
980
+ - `created_at: str` - Дата создания
981
+ - `updated_at: str` - Дата обновления
753
982
 
754
- ### Удаление сделки
983
+ **Важно:** При создании сделки обязательно указывать поле `program` (программа/схема сделки).
755
984
 
756
- ```python
757
- await client.deals.delete(deal_id=200)
758
- # Параметры:
759
- # deal_id: int - Идентификатор сделки
760
- # Возвращает: None
761
- ```
985
+ ### Специфичные методы сделок
762
986
 
763
- ### Применение перехода (изменение статуса)
987
+ #### Применение перехода (изменение статуса)
764
988
 
765
989
  ```python
766
990
  deal = await client.deals.apply_transition(
@@ -780,6 +1004,23 @@ deal = await client.deals.apply_trigger(
780
1004
  # Возвращает: Deal - обновленная сделка
781
1005
  ```
782
1006
 
1007
+ ### Получение всех участников сделки
1008
+
1009
+ Метод `get_all_participants()` возвращает полный список участников сделки:
1010
+
1011
+ ```python
1012
+ participants = await client.deals.get_all_participants(
1013
+ deal_id=200,
1014
+ limit=None, # int: Количество элементов
1015
+ )
1016
+ # Возвращает: list[Employee]
1017
+
1018
+ for employee in participants:
1019
+ print(employee.display_name())
1020
+ ```
1021
+
1022
+ **Примечание:** В отличие от задач и проектов, сделки возвращают только сотрудников (`Employee`).
1023
+
783
1024
  ### Получение аудиторов сделки
784
1025
 
785
1026
  ```python
@@ -813,54 +1054,21 @@ exists = await client.deals.check_exists(deal_params={
813
1054
 
814
1055
  ### Получение полной информации о сделке
815
1056
 
816
- Метод `get_full_details()` позволяет получить сделку со всеми связанными данными за один вызов. Все запросы выполняются параллельно для максимальной производительности.
1057
+ Метод `get_full_details()` для сделок поддерживает следующие специфичные параметры:
817
1058
 
818
1059
  ```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}")
1060
+ details = await client.deals.get_full_details(
1061
+ deal_id=200,
1062
+ include_comments=True, # Загрузить комментарии
1063
+ include_history=True, # Загрузить историю изменений
1064
+ include_status_history=True, # Загрузить историю статусов
1065
+ include_auditors=True, # Загрузить список аудиторов
1066
+ include_responsible_details=True, # Загрузить полные данные ответственного
1067
+ include_contractor_details=True, # Загрузить полные данные контрагента
1068
+ include_related_tasks=True, # Загрузить связанные задачи
1069
+ comments_limit=50, # Лимит комментариев (опционально)
1070
+ history_limit=100 # Лимит записей истории (опционально)
1071
+ )
864
1072
  ```
865
1073
 
866
1074
  **Поля объекта DealFullDetails:**
@@ -873,101 +1081,11 @@ async with MegaplanClient(...) as client:
873
1081
  - `contractor_details: Contractor | None` - Полные данные контрагента
874
1082
  - `related_tasks: list[Task] | None` - Связанные задачи
875
1083
 
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")
1084
+ > **Примечание:** Общее описание метода `get_full_details()` и примеры использования см. в разделе [Общие паттерны работы с сущностями](#общие-паттерны-работы-с-сущностями).
965
1085
 
966
- # Очистить токены
967
- client.auth.clear_tokens()
968
- ```
1086
+ ## Продвинутые возможности
969
1087
 
970
- ## Кэширование сущностей
1088
+ ### Кэширование сущностей
971
1089
 
972
1090
  SDK автоматически кэширует справочные сущности (сотрудники, контрагенты, отделы) для уменьшения количества API запросов и повышения производительности.
973
1091
 
@@ -1014,7 +1132,7 @@ if client._cache:
1014
1132
  - Кэш работает автоматически, не требуя изменений в коде
1015
1133
  - При использовании `expand` уникальные сущности загружаются параллельно
1016
1134
 
1017
- ## Глобальные дефолтные лимиты
1135
+ ### Глобальные дефолтные лимиты
1018
1136
 
1019
1137
  SDK позволяет задать глобальные дефолтные значения для параметров `comments_limit` и `history_limit` на уровне клиента. Эти значения будут применяться ко всем вызовам `get_full_details()` для задач, проектов и сделок, если не переопределены явно.
1020
1138
 
@@ -1131,7 +1249,7 @@ projects_details = await client.projects.get_full_details(
1131
1249
  )
1132
1250
  ```
1133
1251
 
1134
- ## Автоматическая подгрузка связанных сущностей
1252
+ ### Автоматическая подгрузка связанных сущностей
1135
1253
 
1136
1254
  Параметр `expand` позволяет автоматически подгружать связанные сущности (сотрудников, контрагентов, отделы) вместо получения только ID.
1137
1255
 
@@ -1263,7 +1381,196 @@ tasks_full = await client.tasks.list(limit=100, expand=["responsible"])
1263
1381
  - С expand: 2 запроса (1 список + 1 батч на 5 сотрудников)
1264
1382
  - **Экономия: 99 запросов (98%)**
1265
1383
 
1266
- ## Известные ограничения API
1384
+ ### Работа с фильтрами
1385
+
1386
+ SDK предоставляет удобный `FilterBuilder` для создания фильтров с использованием fluent API. Фильтры поддерживаются для задач (`TaskFilter`) и сделок (`TradeFilter`). **Проекты не поддерживают фильтрацию через API.**
1387
+
1388
+ ### Базовое использование
1389
+
1390
+ ```python
1391
+ from megaplan_sdk import TaskFilterBuilder, TradeFilterBuilder
1392
+
1393
+ # Простой текстовый поиск в задачах
1394
+ filter_obj = TaskFilterBuilder().field("name").contains("договор").build()
1395
+ tasks = await client.tasks.list(filter=filter_obj)
1396
+
1397
+ # Простой текстовый поиск в сделках
1398
+ filter_obj = TradeFilterBuilder().field("name").contains("Leader").build()
1399
+ deals = await client.deals.list(filter=filter_obj)
1400
+ ```
1401
+
1402
+ ### Доступные операции для строковых полей
1403
+
1404
+ ```python
1405
+ # Поиск подстроки (рекомендуется для текстового поиска)
1406
+ filter_obj = TaskFilterBuilder().field("name").contains("договор").build()
1407
+
1408
+ # Поиск по началу строки
1409
+ filter_obj = TaskFilterBuilder().field("name").starts_with("Важный").build()
1410
+
1411
+ # Точное совпадение
1412
+ filter_obj = TaskFilterBuilder().field("status").equals("active").build()
1413
+
1414
+ # Исключение подстроки
1415
+ filter_obj = TaskFilterBuilder().field("name").not_contains("архив").build()
1416
+
1417
+ # Не равно
1418
+ filter_obj = TaskFilterBuilder().field("status").not_equals("completed").build()
1419
+ ```
1420
+
1421
+ ### Комбинирование условий
1422
+
1423
+ ```python
1424
+ # Несколько условий с AND
1425
+ filter_obj = (
1426
+ TaskFilterBuilder()
1427
+ .field("name").contains("договор")
1428
+ .and_()
1429
+ .field("name").starts_with("Важный")
1430
+ .build()
1431
+ )
1432
+
1433
+ # Условия с OR
1434
+ filter_obj = (
1435
+ TaskFilterBuilder()
1436
+ .field("name").contains("договор")
1437
+ .or_()
1438
+ .field("name").contains("соглашение")
1439
+ .build()
1440
+ )
1441
+ ```
1442
+
1443
+ ### Использование фильтров по ID
1444
+
1445
+ Помимо `FilterBuilder`, можно использовать сохраненные фильтры по их ID:
1446
+
1447
+ ```python
1448
+ # Фильтр по числовому ID
1449
+ tasks = await client.tasks.list(filter=123)
1450
+
1451
+ # Фильтр по строковому ID
1452
+ tasks = await client.tasks.list(filter="incoming")
1453
+ deals = await client.deals.list(filter="active")
1454
+ ```
1455
+
1456
+ ### Управление фильтрами
1457
+
1458
+ SDK предоставляет методы для работы с сохраненными фильтрами:
1459
+
1460
+ ```python
1461
+ # Получить список всех фильтров для задач
1462
+ filters = await client.filters.list("task")
1463
+
1464
+ # Получить конкретный фильтр
1465
+ filter_obj = await client.filters.get("task", filter_id=123)
1466
+
1467
+ # Создать новый фильтр
1468
+ new_filter = await client.filters.create(
1469
+ "task",
1470
+ filter_id="my_custom_filter",
1471
+ filter_config={
1472
+ "config": {
1473
+ "contentType": "FilterConfig",
1474
+ "termGroup": {
1475
+ "contentType": "FilterTermGroup",
1476
+ "join": "and",
1477
+ "terms": [
1478
+ {
1479
+ "contentType": "FilterTermString",
1480
+ "field": "name",
1481
+ "comparison": "contains",
1482
+ "value": "договор"
1483
+ }
1484
+ ]
1485
+ }
1486
+ }
1487
+ }
1488
+ )
1489
+
1490
+ # Обновить существующий фильтр
1491
+ updated = await client.filters.update("task", filter_id=123, filter_config={...})
1492
+
1493
+ # Экспортировать фильтр
1494
+ export_data = await client.filters.export("task", filter_id=123)
1495
+ ```
1496
+
1497
+ ### Настройка HTTP-клиента
1498
+
1499
+ ```python
1500
+ client = MegaplanClient(
1501
+ base_url="https://my.megaplan.ru",
1502
+ username="user@example.com",
1503
+ password="password",
1504
+ timeout=60.0, # float: Таймаут запросов в секундах (по умолчанию 30.0)
1505
+ max_retries=5 # int: Максимальное количество повторов при 5xx ошибках (по умолчанию 3)
1506
+ )
1507
+ ```
1508
+
1509
+ ### Работа через прокси
1510
+
1511
+ SDK поддерживает работу через HTTP/HTTPS/SOCKS5 прокси-серверы. Это полезно для корпоративных сетей, где все запросы должны проходить через прокси.
1512
+
1513
+ ```python
1514
+ # HTTP прокси с аутентификацией
1515
+ async with MegaplanClient(
1516
+ base_url="https://my.megaplan.ru",
1517
+ username="user@example.com",
1518
+ password="password",
1519
+ proxy="http://login:pass@proxy.corp.local:8080",
1520
+ ) as client:
1521
+ tasks = await client.tasks.list()
1522
+
1523
+ # HTTP прокси без аутентификации
1524
+ client = MegaplanClient(
1525
+ base_url="https://my.megaplan.ru",
1526
+ access_token="token",
1527
+ proxy="http://proxy.corp.local:8080",
1528
+ )
1529
+
1530
+ # HTTPS прокси
1531
+ client = MegaplanClient(
1532
+ base_url="https://my.megaplan.ru",
1533
+ access_token="token",
1534
+ proxy="https://proxy.corp.local:8080",
1535
+ )
1536
+
1537
+ # SOCKS5 прокси (требует httpx[socks])
1538
+ client = MegaplanClient(
1539
+ base_url="https://my.megaplan.ru",
1540
+ access_token="token",
1541
+ proxy="socks5://user:pass@proxy.corp.local:1080",
1542
+ )
1543
+ ```
1544
+
1545
+ **Поддерживаемые форматы прокси:**
1546
+ - `http://proxy:port` - HTTP прокси без аутентификации
1547
+ - `http://user:password@proxy:port` - HTTP прокси с аутентификацией
1548
+ - `https://proxy:port` - HTTPS прокси
1549
+ - `socks5://user:password@proxy:port` - SOCKS5 прокси (требует `pip install httpx[socks]`)
1550
+
1551
+ ### Ручное управление токенами
1552
+
1553
+ ```python
1554
+ # Получить токен доступа
1555
+ token = await client.auth.authenticate("user@example.com", "password")
1556
+ # Возвращает: str - access_token
1557
+
1558
+ # Обновить токен
1559
+ new_token = await client.auth.refresh_token(refresh_token="refresh_token")
1560
+ # Параметры:
1561
+ # refresh_token: str | None - Токен обновления (опционально, используется сохраненный)
1562
+ # Возвращает: str - новый access_token
1563
+
1564
+ # Установить токен вручную
1565
+ client.set_access_token("your_token")
1566
+
1567
+ # Очистить токены
1568
+ client.auth.clear_tokens()
1569
+ ```
1570
+
1571
+ ## Справочная информация
1572
+
1573
+ ### Известные ограничения API
1267
1574
 
1268
1575
  Некоторые эндпоинты Megaplan API имеют ограничения или известные проблемы:
1269
1576
 
@@ -1279,13 +1586,39 @@ API возвращает ошибку 500 при попытке получить
1279
1586
  # comments = await client.contractors.get_comments(contractor_id=123)
1280
1587
 
1281
1588
  # Вместо этого используйте комментарии в сделках контрагента
1282
- deals = await client.deals.list(
1283
- base_on={"contentType": "Contractor", "id": 123}
1284
- )
1589
+ deals = await client.contractors.get_deals(contractor_id=123)
1285
1590
  for deal in deals:
1286
1591
  comments = await client.deals.get_comments(deal.id)
1287
1592
  ```
1288
1593
 
1594
+ ### Получение сделок контрагента
1595
+
1596
+ SDK предоставляет удобный метод `get_deals()` для получения сделок контрагента:
1597
+
1598
+ ```python
1599
+ # Получить все сделки контрагента
1600
+ deals = await client.contractors.get_deals(
1601
+ contractor_id=123,
1602
+ limit=50 # Опционально
1603
+ )
1604
+
1605
+ for deal in deals:
1606
+ print(f"[{deal.id}] {deal.name}")
1607
+ if deal.state:
1608
+ print(f" Статус: {deal.state}")
1609
+ ```
1610
+
1611
+ Это удобнее, чем использование `FilterBuilder`:
1612
+
1613
+ ```python
1614
+ # Альтернатива через FilterBuilder (более сложный способ)
1615
+ from megaplan_sdk import TradeFilterBuilder
1616
+ filter_obj = TradeFilterBuilder().field("contractor").equals(
1617
+ {"contentType": "Contractor", "id": 123}
1618
+ ).build()
1619
+ deals = await client.deals.list(filter=filter_obj)
1620
+ ```
1621
+
1289
1622
  ### Поиск сотрудников
1290
1623
 
1291
1624
  Поиск сотрудников по имени или телефону может работать некорректно и возвращать 0 результатов. Для надежного поиска используйте точный email:
@@ -1298,13 +1631,86 @@ employees = await client.employees.list(q="Иван Иванов")
1298
1631
  employees = await client.employees.list(q="ivan@example.com")
1299
1632
 
1300
1633
  # Или загрузите всех сотрудников и фильтруйте локально
1634
+ ```
1635
+
1636
+ ### Проверка существования сделки (check_exists)
1637
+
1638
+ Метод `check_exists()` для сделок может возвращать ошибки 500 или 422 из-за ограничений API. SDK автоматически обрабатывает эти ошибки и возвращает `False`. Для проверки существования сделки рекомендуется использовать альтернативные методы:
1639
+
1640
+ ```python
1641
+ # Может вернуть 500/422 ошибку
1642
+ # exists = await client.deals.check_exists(query="Deal name")
1643
+
1644
+ # Альтернатива: используйте поиск через list()
1645
+ deals = await client.deals.list(q="Deal name", limit=1)
1646
+ exists = len(deals) > 0
1647
+
1648
+ # Или используйте FilterBuilder
1649
+ from megaplan_sdk import TradeFilterBuilder
1650
+ filter_obj = TradeFilterBuilder().field("name").equals("Deal name").build()
1651
+ deals = await client.deals.list(filter=filter_obj, limit=1)
1652
+ exists = len(deals) > 0
1653
+ ```
1654
+
1655
+ **Примечание:** SDK автоматически нормализует BaseEntity объекты (конвертирует строковые ID в int), но это не решает проблему багов API.
1656
+
1657
+ ### Параметр statuses для задач
1658
+
1659
+ Параметр `statuses` для фильтрации задач по статусам может возвращать ошибку 422 ValidationError из-за ограничений API. Рекомендуется использовать FilterBuilder для надежной фильтрации:
1660
+
1661
+ ```python
1662
+ # Может вернуть 422 ошибку
1663
+ # tasks = await client.tasks.list(statuses=["assigned", "in_progress"])
1664
+
1665
+ # Рекомендуется: используйте FilterBuilder
1666
+ from megaplan_sdk import TaskFilterBuilder
1667
+ filter_obj = TaskFilterBuilder().field_enum("status").in_list(["assigned", "in_progress"]).build()
1668
+ tasks = await client.tasks.list(filter=filter_obj)
1669
+ ```
1670
+
1671
+ ### Параметр baseOn для сделок
1672
+
1673
+ Параметр `baseOn` для фильтрации сделок по связанной сущности может возвращать ошибку 422 ValidationError из-за ограничений API. SDK автоматически нормализует BaseEntity объекты (конвертирует строковые ID в int), но это не всегда решает проблему:
1674
+
1675
+ ```python
1676
+ # Может вернуть 422 ошибку
1677
+ # deals = await client.deals.list(base_on={"contentType": "Contractor", "id": 123})
1678
+
1679
+ # Альтернатива: используйте FilterBuilder
1680
+ from megaplan_sdk import TradeFilterBuilder
1681
+ filter_obj = TradeFilterBuilder().field("contractor").equals({"contentType": "Contractor", "id": 123}).build()
1682
+ deals = await client.deals.list(filter=filter_obj)
1683
+ ```
1684
+
1685
+ ### Пагинация контрагентов
1686
+
1687
+ Пагинация через `page_after`, `page_before`, `page_with` для контрагентов может возвращать ошибку 422 ValidationError из-за ограничений API. SDK автоматически нормализует BaseEntity объекты, но рекомендуется использовать `limit` и ручную итерацию:
1688
+
1689
+ ```python
1690
+ # Может вернуть 422 ошибку
1691
+ # contractors = await client.contractors.list(page_after={"contentType": "Contractor", "id": 123})
1692
+
1693
+ # Рекомендуется: используйте limit и iterate()
1694
+ async for contractor in client.contractors.iterate(limit=50):
1695
+ # Обработка контрагента
1696
+ pass
1697
+ ```
1698
+
1699
+ ### Нормализация BaseEntity
1700
+
1701
+ SDK автоматически нормализует BaseEntity объекты во всех параметрах:
1702
+ - Конвертирует строковые ID в int (где возможно)
1703
+ - Обеспечивает правильный формат `contentType` и `id`
1704
+ - Применяется к параметрам: `page_after`, `page_before`, `page_with`, `baseOn`, вложенным объектам в `deal` для `check_exists()`
1705
+
1706
+ Это помогает избежать некоторых ошибок валидации, но не решает все проблемы API.
1301
1707
  all_employees = []
1302
1708
  async for emp in client.employees.iterate():
1303
1709
  if "Иван" in emp.first_name:
1304
1710
  all_employees.append(emp)
1305
1711
  ```
1306
1712
 
1307
- ## Архитектура
1713
+ ### Архитектура
1308
1714
 
1309
1715
  SDK спроектирован с учетом модульности:
1310
1716
 
@@ -1328,13 +1734,13 @@ class MegaplanClient:
1328
1734
  self.new_resource = NewResource(self._http)
1329
1735
  ```
1330
1736
 
1331
- ## Требования
1737
+ ### Требования
1332
1738
 
1333
1739
  - Python 3.11+
1334
1740
  - httpx >= 0.25.0
1335
1741
  - pydantic >= 2.0.0
1336
1742
 
1337
- ## Разработка
1743
+ ### Разработка
1338
1744
 
1339
1745
  ### Установка для разработки
1340
1746