megaplan-sdk 0.2.1__tar.gz → 0.2.3__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.2.1/src/megaplan_sdk.egg-info → megaplan_sdk-0.2.3}/PKG-INFO +108 -18
  2. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/README.md +107 -17
  3. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/pyproject.toml +1 -1
  4. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/__init__.py +6 -2
  5. megaplan_sdk-0.2.3/src/megaplan_sdk/constants.py +42 -0
  6. megaplan_sdk-0.2.3/src/megaplan_sdk/models/base.py +27 -0
  7. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/models/comment.py +1 -1
  8. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/models/common.py +39 -6
  9. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/models/contractor.py +1 -1
  10. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/models/deal.py +36 -10
  11. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/models/department.py +1 -1
  12. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/models/employee.py +1 -1
  13. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/models/filter.py +2 -2
  14. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/models/group.py +1 -1
  15. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/models/milestone.py +1 -1
  16. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/models/project.py +2 -2
  17. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/models/task.py +9 -2
  18. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/resources/comments.py +61 -4
  19. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/resources/deals.py +29 -19
  20. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/resources/full_details.py +15 -0
  21. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/resources/projects.py +27 -0
  22. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/resources/tasks.py +27 -1
  23. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3/src/megaplan_sdk.egg-info}/PKG-INFO +108 -18
  24. megaplan_sdk-0.2.1/src/megaplan_sdk/constants.py +0 -17
  25. megaplan_sdk-0.2.1/src/megaplan_sdk/models/base.py +0 -16
  26. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/LICENSE +0 -0
  27. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/setup.cfg +0 -0
  28. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/auth.py +0 -0
  29. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/cache.py +0 -0
  30. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/client.py +0 -0
  31. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/exceptions.py +0 -0
  32. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/filter_builder.py +0 -0
  33. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/helpers.py +0 -0
  34. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/http_client.py +0 -0
  35. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/logging_config.py +0 -0
  36. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/models/__init__.py +0 -0
  37. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/models/participant.py +0 -0
  38. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/resources/__init__.py +0 -0
  39. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/resources/auth.py +0 -0
  40. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/resources/base.py +0 -0
  41. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/resources/contractors.py +0 -0
  42. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/resources/departments.py +0 -0
  43. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/resources/employees.py +0 -0
  44. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/resources/filters.py +0 -0
  45. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk/types.py +0 -0
  46. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk.egg-info/SOURCES.txt +0 -0
  47. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk.egg-info/dependency_links.txt +0 -0
  48. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/src/megaplan_sdk.egg-info/requires.txt +0 -0
  49. {megaplan_sdk-0.2.1 → megaplan_sdk-0.2.3}/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.2.1
3
+ Version: 0.2.3
4
4
  Summary: Professional Python SDK for Megaplan API v3
5
5
  Author-email: Maxim Borzov <max@borzov.com>
6
6
  License-Expression: MIT
@@ -73,6 +73,7 @@ Dynamic: license-file
73
73
  - [Глобальные дефолтные лимиты](#глобальные-дефолтные-лимиты)
74
74
  - [Автоматическая подгрузка связанных сущностей](#автоматическая-подгрузка-связанных-сущностей)
75
75
  - [Работа с фильтрами](#работа-с-фильтрами)
76
+ - [Работа с комментариями](#работа-с-комментариями)
76
77
  - [Настройка HTTP-клиента](#настройка-http-клиента)
77
78
  - [Работа через прокси](#работа-через-прокси)
78
79
  - [Ручное управление токенами](#ручное-управление-токенами)
@@ -314,7 +315,7 @@ entity = await client.{resource}.update(
314
315
  ```python
315
316
  task = await client.tasks.update(task_id=42, task_data={"status": "completed"})
316
317
  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
+ deal = await client.deals.update(deal_id=200, deal_data={"price": {"currency": "RUB", "value": 60000}})
318
319
  ```
319
320
 
320
321
  #### Удаление (`delete`)
@@ -429,7 +430,7 @@ print(details.deal.name) # для сделок
429
430
  # Связанные данные
430
431
  if details.comments:
431
432
  for comment in details.comments:
432
- print(comment.text)
433
+ print(comment.content)
433
434
 
434
435
  if details.history:
435
436
  print(f"Записей в истории: {len(details.history)}")
@@ -517,8 +518,39 @@ tasks = await client.tasks.list(filter=filter_obj)
517
518
  - `tags: list[BaseEntity]` - Теги
518
519
  - `attaches: list[BaseEntity]` - Вложения (файлы)
519
520
  - `todos: list[BaseEntity]` - Подзадачи-чеклисты
520
- - `created_at: str` - Дата создания
521
- - `updated_at: str` - Дата обновления
521
+ - `time_created: str` - Дата создания (API поле `timeCreated`)
522
+ - `time_updated: str` - Дата обновления (API поле `timeUpdated`)
523
+ - `activity: str | None` - Дата последней активности (API поле `activity`)
524
+ - `last_comment_time_created: str | None` - Время последнего комментария (API поле `lastCommentTimeCreated`)
525
+ - `status_change_time: str | None` - Время смены статуса (API поле `statusChangeTime`)
526
+ - `actual_start: str | None` - Фактическое время начала (API поле `actualStart`)
527
+ - `last_view: str | None` - Время последнего просмотра (API поле `lastView`)
528
+
529
+ ### Фильтрация задач по временным полям
530
+
531
+ Временны́е поля (`activity`, `lastCommentTimeCreated` и др.) не возвращаются в `tasks.list()` по умолчанию.
532
+ Используйте константу `DEFAULT_TASK_LIST_FIELDS`, чтобы запросить их явно:
533
+
534
+ ```python
535
+ from megaplan_sdk import DEFAULT_TASK_LIST_FIELDS
536
+
537
+ # Запросить задачи с временными полями (activity, lastCommentTimeCreated и др.)
538
+ tasks = await client.tasks.list(
539
+ limit=50,
540
+ fields=list(DEFAULT_TASK_LIST_FIELDS),
541
+ )
542
+ for task in tasks:
543
+ print(f"{task.name}: активность {task.activity}")
544
+ ```
545
+
546
+ > **Примечание:** Сортировка по `"timeUpdated"` не поддерживается API — используйте `"activity"`:
547
+ >
548
+ > ```python
549
+ > tasks = await client.tasks.list(
550
+ > sort_by=[{"fieldName": "activity", "order": "desc"}],
551
+ > fields=list(DEFAULT_TASK_LIST_FIELDS),
552
+ > )
553
+ > ```
522
554
 
523
555
  ### Упрощенные методы создания
524
556
 
@@ -775,8 +807,8 @@ milestone = await client.projects.add_milestone(
775
807
  - `tags: list[BaseEntity]` - Теги
776
808
  - `attaches: list[BaseEntity]` - Вложения
777
809
  - `todos: list[BaseEntity]` - Подзадачи-чеклисты
778
- - `created_at: str` - Дата создания
779
- - `updated_at: str` - Дата обновления
810
+ - `time_created: str` - Дата создания (API поле `timeCreated`)
811
+ - `time_updated: str` - Дата обновления (API поле `timeUpdated`)
780
812
 
781
813
  ### Упрощенные методы создания
782
814
 
@@ -967,18 +999,26 @@ deals = await client.deals.list(filter=filter_obj)
967
999
  ### Поля модели Deal
968
1000
  - `id: int` - Идентификатор сделки
969
1001
  - `name: str` - Название сделки
1002
+ - `number: str` - Номер сделки
1003
+ - `short_description: str` - Краткое описание
970
1004
  - `program: BaseEntity` - Программа (схема сделки)
971
1005
  - `state: ProgramState` - Текущий статус в программе
972
1006
  - `contractor: BaseEntity` - Контрагент (ContractorCompany/ContractorHuman)
973
- - `responsible: BaseEntity` - Ответственный (Employee)
974
- - `sum_base: float` - Сумма сделки
1007
+ - `manager: BaseEntity` - Ответственный (Employee, API поле `manager`)
1008
+ - `price: Money` - Сумма сделки (объект с полями `currency`, `value`)
1009
+ - `cost: Money` - Стоимость
1010
+ - `debt: Money` - Долг
1011
+ - `result: str` - Результат (`"positive"`, `"negative"`, `null`)
975
1012
  - `currency: BaseEntity` - Валюта
976
1013
  - `deadline: str` - Срок
977
- - `description: str` - Описание
1014
+ - `description: str` - Описание (только в `deals.get()`, не в списке)
978
1015
  - `tags: list[BaseEntity]` - Теги
979
1016
  - `attaches: list[BaseEntity]` - Вложения
980
- - `created_at: str` - Дата создания
981
- - `updated_at: str` - Дата обновления
1017
+ - `time_created: str` - Дата создания (API поле `timeCreated`)
1018
+ - `time_updated: str` - Дата обновления (API поле `timeUpdated`)
1019
+ - `state_time_updated: str` - Дата последнего изменения статуса
1020
+
1021
+ > **Примечание:** Поля `description`, `deadline` и пользовательские поля доступны только при запросе отдельной сделки через `deals.get(id)`, но не в списке.
982
1022
 
983
1023
  **Важно:** При создании сделки обязательно указывать поле `program` (программа/схема сделки).
984
1024
 
@@ -1063,7 +1103,7 @@ details = await client.deals.get_full_details(
1063
1103
  include_history=True, # Загрузить историю изменений
1064
1104
  include_status_history=True, # Загрузить историю статусов
1065
1105
  include_auditors=True, # Загрузить список аудиторов
1066
- include_responsible_details=True, # Загрузить полные данные ответственного
1106
+ include_manager_details=True, # Загрузить полные данные ответственного
1067
1107
  include_contractor_details=True, # Загрузить полные данные контрагента
1068
1108
  include_related_tasks=True, # Загрузить связанные задачи
1069
1109
  comments_limit=50, # Лимит комментариев (опционально)
@@ -1077,7 +1117,7 @@ details = await client.deals.get_full_details(
1077
1117
  - `history: list[dict] | None` - История изменений
1078
1118
  - `status_history: list[dict] | None` - История статусов
1079
1119
  - `auditors: list[dict] | None` - Аудиторы
1080
- - `responsible_details: Employee | None` - Полные данные ответственного
1120
+ - `manager_details: Employee | None` - Полные данные ответственного
1081
1121
  - `contractor_details: Contractor | None` - Полные данные контрагента
1082
1122
  - `related_tasks: list[Task] | None` - Связанные задачи
1083
1123
 
@@ -1278,14 +1318,14 @@ for task_full in tasks_full:
1278
1318
  ### Использование expand в сделках
1279
1319
 
1280
1320
  ```python
1281
- deals_full = await client.deals.list(limit=10, expand=["responsible", "contractor"])
1321
+ deals_full = await client.deals.list(limit=10, expand=["manager", "contractor"])
1282
1322
 
1283
1323
  for deal_full in deals_full:
1284
1324
  deal = deal_full.deal
1285
1325
  print(f"Сделка: {deal.name}")
1286
1326
 
1287
- if deal_full.responsible_details:
1288
- print(f"Ответственный: {deal_full.responsible_details.display_name()}")
1327
+ if deal_full.manager_details:
1328
+ print(f"Ответственный: {deal_full.manager_details.display_name()}")
1289
1329
 
1290
1330
  if deal_full.contractor_details:
1291
1331
  print(f"Контрагент: {deal_full.contractor_details.display_name()}")
@@ -1296,7 +1336,7 @@ for deal_full in deals_full:
1296
1336
  ```
1297
1337
 
1298
1338
  **Поддерживаемые поля для expand в сделках:**
1299
- - `responsible` - ответственный сотрудник
1339
+ - `manager` - ответственный сотрудник
1300
1340
  - `contractor` - контрагент
1301
1341
 
1302
1342
  ### Использование expand в проектах
@@ -1494,6 +1534,56 @@ updated = await client.filters.update("task", filter_id=123, filter_config={...}
1494
1534
  export_data = await client.filters.export("task", filter_id=123)
1495
1535
  ```
1496
1536
 
1537
+ ### Работа с комментариями
1538
+
1539
+ Комментарии доступны для задач, проектов и сделок через `client.comments`.
1540
+
1541
+ #### Получение комментариев
1542
+
1543
+ ```python
1544
+ # Комментарии задачи (entity_type по умолчанию "task")
1545
+ comments = await client.comments.list(entity_id=42)
1546
+
1547
+ # Комментарии проекта
1548
+ comments = await client.comments.list(entity_id=5, entity_type="project")
1549
+
1550
+ # Комментарии сделки
1551
+ comments = await client.comments.list(entity_id=200, entity_type="deal")
1552
+
1553
+ # Автоматическая пагинация
1554
+ async for comment in client.comments.iterate(entity_id=42):
1555
+ print(comment.content)
1556
+ ```
1557
+
1558
+ #### Подгрузка авторов через expand
1559
+
1560
+ API Мегаплана не раскрывает поле `owner` (автор комментария) в списке комментариев.
1561
+ Используйте `expand=["owner"]`, чтобы SDK дозагрузил авторов отдельными запросами (с кэшированием):
1562
+
1563
+ ```python
1564
+ # Комментарии задачи с именами авторов
1565
+ comments = await client.comments.list(entity_id=42, expand=["owner"])
1566
+ for comment in comments:
1567
+ author_name = comment.owner.name if comment.owner else "неизвестен"
1568
+ print(f"{author_name}: {comment.content}")
1569
+ ```
1570
+
1571
+ #### Создание комментария
1572
+
1573
+ ```python
1574
+ # Комментарий к задаче
1575
+ comment = await client.comments.create(entity_id=42, comment_data={"text": "Текст комментария"})
1576
+
1577
+ # Комментарий к проекту
1578
+ comment = await client.comments.create(entity_id=5, comment_data={"text": "Текст"}, entity_type="project")
1579
+
1580
+ # Комментарий к сделке
1581
+ comment = await client.comments.create(entity_id=200, comment_data={"text": "Текст"}, entity_type="deal")
1582
+ ```
1583
+
1584
+ > **Ограничение:** Комментарии контрагентов не поддерживаются API (возвращает 500).
1585
+ > Используйте комментарии в связанных сделках или задачах.
1586
+
1497
1587
  ### Настройка HTTP-клиента
1498
1588
 
1499
1589
  ```python
@@ -41,6 +41,7 @@
41
41
  - [Глобальные дефолтные лимиты](#глобальные-дефолтные-лимиты)
42
42
  - [Автоматическая подгрузка связанных сущностей](#автоматическая-подгрузка-связанных-сущностей)
43
43
  - [Работа с фильтрами](#работа-с-фильтрами)
44
+ - [Работа с комментариями](#работа-с-комментариями)
44
45
  - [Настройка HTTP-клиента](#настройка-http-клиента)
45
46
  - [Работа через прокси](#работа-через-прокси)
46
47
  - [Ручное управление токенами](#ручное-управление-токенами)
@@ -282,7 +283,7 @@ entity = await client.{resource}.update(
282
283
  ```python
283
284
  task = await client.tasks.update(task_id=42, task_data={"status": "completed"})
284
285
  project = await client.projects.update(project_id=5, project_data={"name": "Новое название"})
285
- deal = await client.deals.update(deal_id=200, deal_data={"sum_base": 60000.0})
286
+ deal = await client.deals.update(deal_id=200, deal_data={"price": {"currency": "RUB", "value": 60000}})
286
287
  ```
287
288
 
288
289
  #### Удаление (`delete`)
@@ -397,7 +398,7 @@ print(details.deal.name) # для сделок
397
398
  # Связанные данные
398
399
  if details.comments:
399
400
  for comment in details.comments:
400
- print(comment.text)
401
+ print(comment.content)
401
402
 
402
403
  if details.history:
403
404
  print(f"Записей в истории: {len(details.history)}")
@@ -485,8 +486,39 @@ tasks = await client.tasks.list(filter=filter_obj)
485
486
  - `tags: list[BaseEntity]` - Теги
486
487
  - `attaches: list[BaseEntity]` - Вложения (файлы)
487
488
  - `todos: list[BaseEntity]` - Подзадачи-чеклисты
488
- - `created_at: str` - Дата создания
489
- - `updated_at: str` - Дата обновления
489
+ - `time_created: str` - Дата создания (API поле `timeCreated`)
490
+ - `time_updated: str` - Дата обновления (API поле `timeUpdated`)
491
+ - `activity: str | None` - Дата последней активности (API поле `activity`)
492
+ - `last_comment_time_created: str | None` - Время последнего комментария (API поле `lastCommentTimeCreated`)
493
+ - `status_change_time: str | None` - Время смены статуса (API поле `statusChangeTime`)
494
+ - `actual_start: str | None` - Фактическое время начала (API поле `actualStart`)
495
+ - `last_view: str | None` - Время последнего просмотра (API поле `lastView`)
496
+
497
+ ### Фильтрация задач по временным полям
498
+
499
+ Временны́е поля (`activity`, `lastCommentTimeCreated` и др.) не возвращаются в `tasks.list()` по умолчанию.
500
+ Используйте константу `DEFAULT_TASK_LIST_FIELDS`, чтобы запросить их явно:
501
+
502
+ ```python
503
+ from megaplan_sdk import DEFAULT_TASK_LIST_FIELDS
504
+
505
+ # Запросить задачи с временными полями (activity, lastCommentTimeCreated и др.)
506
+ tasks = await client.tasks.list(
507
+ limit=50,
508
+ fields=list(DEFAULT_TASK_LIST_FIELDS),
509
+ )
510
+ for task in tasks:
511
+ print(f"{task.name}: активность {task.activity}")
512
+ ```
513
+
514
+ > **Примечание:** Сортировка по `"timeUpdated"` не поддерживается API — используйте `"activity"`:
515
+ >
516
+ > ```python
517
+ > tasks = await client.tasks.list(
518
+ > sort_by=[{"fieldName": "activity", "order": "desc"}],
519
+ > fields=list(DEFAULT_TASK_LIST_FIELDS),
520
+ > )
521
+ > ```
490
522
 
491
523
  ### Упрощенные методы создания
492
524
 
@@ -743,8 +775,8 @@ milestone = await client.projects.add_milestone(
743
775
  - `tags: list[BaseEntity]` - Теги
744
776
  - `attaches: list[BaseEntity]` - Вложения
745
777
  - `todos: list[BaseEntity]` - Подзадачи-чеклисты
746
- - `created_at: str` - Дата создания
747
- - `updated_at: str` - Дата обновления
778
+ - `time_created: str` - Дата создания (API поле `timeCreated`)
779
+ - `time_updated: str` - Дата обновления (API поле `timeUpdated`)
748
780
 
749
781
  ### Упрощенные методы создания
750
782
 
@@ -935,18 +967,26 @@ deals = await client.deals.list(filter=filter_obj)
935
967
  ### Поля модели Deal
936
968
  - `id: int` - Идентификатор сделки
937
969
  - `name: str` - Название сделки
970
+ - `number: str` - Номер сделки
971
+ - `short_description: str` - Краткое описание
938
972
  - `program: BaseEntity` - Программа (схема сделки)
939
973
  - `state: ProgramState` - Текущий статус в программе
940
974
  - `contractor: BaseEntity` - Контрагент (ContractorCompany/ContractorHuman)
941
- - `responsible: BaseEntity` - Ответственный (Employee)
942
- - `sum_base: float` - Сумма сделки
975
+ - `manager: BaseEntity` - Ответственный (Employee, API поле `manager`)
976
+ - `price: Money` - Сумма сделки (объект с полями `currency`, `value`)
977
+ - `cost: Money` - Стоимость
978
+ - `debt: Money` - Долг
979
+ - `result: str` - Результат (`"positive"`, `"negative"`, `null`)
943
980
  - `currency: BaseEntity` - Валюта
944
981
  - `deadline: str` - Срок
945
- - `description: str` - Описание
982
+ - `description: str` - Описание (только в `deals.get()`, не в списке)
946
983
  - `tags: list[BaseEntity]` - Теги
947
984
  - `attaches: list[BaseEntity]` - Вложения
948
- - `created_at: str` - Дата создания
949
- - `updated_at: str` - Дата обновления
985
+ - `time_created: str` - Дата создания (API поле `timeCreated`)
986
+ - `time_updated: str` - Дата обновления (API поле `timeUpdated`)
987
+ - `state_time_updated: str` - Дата последнего изменения статуса
988
+
989
+ > **Примечание:** Поля `description`, `deadline` и пользовательские поля доступны только при запросе отдельной сделки через `deals.get(id)`, но не в списке.
950
990
 
951
991
  **Важно:** При создании сделки обязательно указывать поле `program` (программа/схема сделки).
952
992
 
@@ -1031,7 +1071,7 @@ details = await client.deals.get_full_details(
1031
1071
  include_history=True, # Загрузить историю изменений
1032
1072
  include_status_history=True, # Загрузить историю статусов
1033
1073
  include_auditors=True, # Загрузить список аудиторов
1034
- include_responsible_details=True, # Загрузить полные данные ответственного
1074
+ include_manager_details=True, # Загрузить полные данные ответственного
1035
1075
  include_contractor_details=True, # Загрузить полные данные контрагента
1036
1076
  include_related_tasks=True, # Загрузить связанные задачи
1037
1077
  comments_limit=50, # Лимит комментариев (опционально)
@@ -1045,7 +1085,7 @@ details = await client.deals.get_full_details(
1045
1085
  - `history: list[dict] | None` - История изменений
1046
1086
  - `status_history: list[dict] | None` - История статусов
1047
1087
  - `auditors: list[dict] | None` - Аудиторы
1048
- - `responsible_details: Employee | None` - Полные данные ответственного
1088
+ - `manager_details: Employee | None` - Полные данные ответственного
1049
1089
  - `contractor_details: Contractor | None` - Полные данные контрагента
1050
1090
  - `related_tasks: list[Task] | None` - Связанные задачи
1051
1091
 
@@ -1246,14 +1286,14 @@ for task_full in tasks_full:
1246
1286
  ### Использование expand в сделках
1247
1287
 
1248
1288
  ```python
1249
- deals_full = await client.deals.list(limit=10, expand=["responsible", "contractor"])
1289
+ deals_full = await client.deals.list(limit=10, expand=["manager", "contractor"])
1250
1290
 
1251
1291
  for deal_full in deals_full:
1252
1292
  deal = deal_full.deal
1253
1293
  print(f"Сделка: {deal.name}")
1254
1294
 
1255
- if deal_full.responsible_details:
1256
- print(f"Ответственный: {deal_full.responsible_details.display_name()}")
1295
+ if deal_full.manager_details:
1296
+ print(f"Ответственный: {deal_full.manager_details.display_name()}")
1257
1297
 
1258
1298
  if deal_full.contractor_details:
1259
1299
  print(f"Контрагент: {deal_full.contractor_details.display_name()}")
@@ -1264,7 +1304,7 @@ for deal_full in deals_full:
1264
1304
  ```
1265
1305
 
1266
1306
  **Поддерживаемые поля для expand в сделках:**
1267
- - `responsible` - ответственный сотрудник
1307
+ - `manager` - ответственный сотрудник
1268
1308
  - `contractor` - контрагент
1269
1309
 
1270
1310
  ### Использование expand в проектах
@@ -1462,6 +1502,56 @@ updated = await client.filters.update("task", filter_id=123, filter_config={...}
1462
1502
  export_data = await client.filters.export("task", filter_id=123)
1463
1503
  ```
1464
1504
 
1505
+ ### Работа с комментариями
1506
+
1507
+ Комментарии доступны для задач, проектов и сделок через `client.comments`.
1508
+
1509
+ #### Получение комментариев
1510
+
1511
+ ```python
1512
+ # Комментарии задачи (entity_type по умолчанию "task")
1513
+ comments = await client.comments.list(entity_id=42)
1514
+
1515
+ # Комментарии проекта
1516
+ comments = await client.comments.list(entity_id=5, entity_type="project")
1517
+
1518
+ # Комментарии сделки
1519
+ comments = await client.comments.list(entity_id=200, entity_type="deal")
1520
+
1521
+ # Автоматическая пагинация
1522
+ async for comment in client.comments.iterate(entity_id=42):
1523
+ print(comment.content)
1524
+ ```
1525
+
1526
+ #### Подгрузка авторов через expand
1527
+
1528
+ API Мегаплана не раскрывает поле `owner` (автор комментария) в списке комментариев.
1529
+ Используйте `expand=["owner"]`, чтобы SDK дозагрузил авторов отдельными запросами (с кэшированием):
1530
+
1531
+ ```python
1532
+ # Комментарии задачи с именами авторов
1533
+ comments = await client.comments.list(entity_id=42, expand=["owner"])
1534
+ for comment in comments:
1535
+ author_name = comment.owner.name if comment.owner else "неизвестен"
1536
+ print(f"{author_name}: {comment.content}")
1537
+ ```
1538
+
1539
+ #### Создание комментария
1540
+
1541
+ ```python
1542
+ # Комментарий к задаче
1543
+ comment = await client.comments.create(entity_id=42, comment_data={"text": "Текст комментария"})
1544
+
1545
+ # Комментарий к проекту
1546
+ comment = await client.comments.create(entity_id=5, comment_data={"text": "Текст"}, entity_type="project")
1547
+
1548
+ # Комментарий к сделке
1549
+ comment = await client.comments.create(entity_id=200, comment_data={"text": "Текст"}, entity_type="deal")
1550
+ ```
1551
+
1552
+ > **Ограничение:** Комментарии контрагентов не поддерживаются API (возвращает 500).
1553
+ > Используйте комментарии в связанных сделках или задачах.
1554
+
1465
1555
  ### Настройка HTTP-клиента
1466
1556
 
1467
1557
  ```python
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "megaplan-sdk"
7
- version = "0.2.1"
7
+ version = "0.2.3"
8
8
  description = "Professional Python SDK for Megaplan API v3"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,6 +1,7 @@
1
1
  """Megaplan Python SDK - Professional SDK for Megaplan API v3."""
2
2
 
3
3
  from megaplan_sdk.client import MegaplanClient
4
+ from megaplan_sdk.constants import DEFAULT_TASK_LIST_FIELDS
4
5
  from megaplan_sdk.exceptions import (
5
6
  AuthenticationError,
6
7
  AuthorizationError,
@@ -26,7 +27,7 @@ from megaplan_sdk.helpers import (
26
27
  )
27
28
  from megaplan_sdk.logging_config import setup_logging
28
29
  from megaplan_sdk.models.comment import Comment
29
- from megaplan_sdk.models.common import DateTime
30
+ from megaplan_sdk.models.common import DateTime, Money
30
31
  from megaplan_sdk.models.contractor import Contractor, ContractorCompany, ContractorHuman
31
32
  from megaplan_sdk.models.deal import Deal, DealFullDetails
32
33
  from megaplan_sdk.models.department import Department
@@ -66,6 +67,7 @@ __all__ = [
66
67
  "Deal",
67
68
  "DealFullDetails",
68
69
  "Comment",
70
+ "Money",
69
71
  "Contractor",
70
72
  "ContractorCompany",
71
73
  "ContractorHuman",
@@ -100,6 +102,8 @@ __all__ = [
100
102
  "TaskFilterBuilder",
101
103
  "TradeFilterBuilder",
102
104
  "ProjectFilterBuilder",
105
+ # Constants
106
+ "DEFAULT_TASK_LIST_FIELDS",
103
107
  ]
104
108
 
105
- __version__ = "0.1.0"
109
+ __version__ = "0.2.3"
@@ -0,0 +1,42 @@
1
+ """Constants for Megaplan SDK."""
2
+
3
+
4
+ class ContentType:
5
+ """Content type constants for Megaplan API entities."""
6
+
7
+ TASK = "Task"
8
+ PROJECT = "Project"
9
+ DEAL = "Deal"
10
+ EMPLOYEE = "Employee"
11
+ CONTRACTOR = "Contractor"
12
+ CONTRACTOR_COMPANY = "ContractorCompany"
13
+ CONTRACTOR_HUMAN = "ContractorHuman"
14
+ CONTRACTOR_CATEGORY = "ContractorCategory"
15
+ DEPARTMENT = "Department"
16
+ COMMENT = "Comment"
17
+ GROUP = "Group"
18
+
19
+
20
+ # Task fields users commonly try to sort by that the Megaplan API rejects (422).
21
+ # Maps the unsupported field name to the supported replacement to suggest.
22
+ UNSUPPORTED_TASK_SORT_FIELDS: dict[str, str] = {
23
+ "timeUpdated": "activity",
24
+ "updatedAt": "activity",
25
+ }
26
+
27
+ # Recommended `fields` set for tasks.list() so that date fields are populated.
28
+ # Megaplan list endpoints omit these unless explicitly requested, which makes
29
+ # client-side time-window filtering silently return nothing (#8).
30
+ # Only fields confirmed to exist on Task are included (no "timeUpdated" — see #7).
31
+ DEFAULT_TASK_LIST_FIELDS: tuple[str, ...] = (
32
+ "name",
33
+ "status",
34
+ "timeCreated",
35
+ "activity",
36
+ "lastCommentTimeCreated",
37
+ "statusChangeTime",
38
+ "actualStart",
39
+ "owner",
40
+ "responsible",
41
+ "commentsCount",
42
+ )
@@ -0,0 +1,27 @@
1
+ """Base models for Megaplan SDK."""
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
5
+
6
+ class BaseEntity(BaseModel):
7
+ """Base entity with contentType, id, and optional name.
8
+
9
+ All Megaplan entities have ``contentType`` and ``id`` fields.
10
+ Many API responses also include a ``name`` field even in reference
11
+ objects, so it is captured here to avoid silent data loss.
12
+
13
+ Unknown fields from the API are preserved in ``model_extra`` via
14
+ ``extra="allow"``, which enables forward compatibility and prevents
15
+ silent data loss when the API returns new fields.
16
+
17
+ Attributes:
18
+ content_type: Entity type identifier (e.g., "Employee", "Task").
19
+ id: Entity numeric identifier.
20
+ name: Optional display name returned by the API in many contexts.
21
+ """
22
+
23
+ content_type: str = Field(alias="contentType")
24
+ id: int
25
+ name: str | None = None
26
+
27
+ model_config = ConfigDict(populate_by_name=True, extra="allow")
@@ -40,4 +40,4 @@ class Comment(BaseEntity, TimestampMixin):
40
40
  is_dropped: bool | None = Field(alias="isDropped", default=None)
41
41
  completed: int | None = None
42
42
 
43
- model_config = ConfigDict(populate_by_name=True, extra="ignore")
43
+ model_config = ConfigDict(populate_by_name=True, extra="allow")
@@ -50,7 +50,7 @@ class File(BaseModel):
50
50
  name: str | None = None
51
51
  size: int | None = None
52
52
 
53
- model_config = ConfigDict(populate_by_name=True, extra="ignore")
53
+ model_config = ConfigDict(populate_by_name=True, extra="allow")
54
54
 
55
55
 
56
56
  class DateTime(BaseModel):
@@ -66,7 +66,37 @@ class DateTime(BaseModel):
66
66
  content_type: str = Field(alias="contentType", default="DateTime")
67
67
  value: str
68
68
 
69
- model_config = ConfigDict(populate_by_name=True, extra="ignore")
69
+ model_config = ConfigDict(populate_by_name=True, extra="allow")
70
+
71
+
72
+ class Money(BaseModel):
73
+ """Money model for monetary value fields.
74
+
75
+ Megaplan API returns monetary values as structured objects with
76
+ currency, value, and optional exchange rate information.
77
+
78
+ Attributes:
79
+ content_type: Always "Money".
80
+ currency: ISO 4217 currency code (e.g., "RUB", "USD").
81
+ value: Amount in the original currency.
82
+ value_in_main: Amount converted to the main company currency.
83
+ rate: Exchange rate relative to the main currency.
84
+
85
+ Example:
86
+ >>> money = Money(contentType="Money", currency="RUB", value=18055000)
87
+ >>> money.value
88
+ 18055000
89
+ >>> money.currency
90
+ 'RUB'
91
+ """
92
+
93
+ content_type: str = Field(alias="contentType", default="Money")
94
+ currency: str = ""
95
+ value: float | int | None = None
96
+ value_in_main: float | int | None = Field(None, alias="valueInMain")
97
+ rate: float | None = None
98
+
99
+ model_config = ConfigDict(populate_by_name=True, extra="allow")
70
100
 
71
101
 
72
102
  class SortField(BaseModel):
@@ -84,10 +114,13 @@ class SortField(BaseModel):
84
114
  class TimestampMixin(BaseModel):
85
115
  """Mixin for entities with creation and update timestamps.
86
116
 
117
+ Megaplan API returns timestamps using the field names ``timeCreated``
118
+ and ``timeUpdated`` (not ``createdAt``/``updatedAt``).
119
+
87
120
  Attributes:
88
- created_at: Entity creation timestamp.
89
- updated_at: Entity last update timestamp.
121
+ time_created: Entity creation timestamp (API field: ``timeCreated``).
122
+ time_updated: Entity last update timestamp (API field: ``timeUpdated``).
90
123
  """
91
124
 
92
- created_at: str | DateTime | None = Field(alias="createdAt", default=None)
93
- updated_at: str | DateTime | None = Field(alias="updatedAt", default=None)
125
+ time_created: str | DateTime | None = Field(alias="timeCreated", default=None)
126
+ time_updated: str | DateTime | None = Field(alias="timeUpdated", default=None)
@@ -40,7 +40,7 @@ class Contractor(BaseEntity, TimestampMixin):
40
40
  tags: list[BaseEntity | str] | None = None # Can be Tag entities or strings
41
41
  custom_fields: dict[str, Any] | None = Field(alias="customFields", default=None)
42
42
 
43
- model_config = ConfigDict(populate_by_name=True, extra="ignore")
43
+ model_config = ConfigDict(populate_by_name=True, extra="allow")
44
44
 
45
45
  def display_name(self) -> str:
46
46
  """Get display name for contractor.