megaplan-sdk 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- megaplan_sdk/__init__.py +67 -0
- megaplan_sdk/auth.py +185 -0
- megaplan_sdk/cache.py +192 -0
- megaplan_sdk/client.py +201 -0
- megaplan_sdk/constants.py +16 -0
- megaplan_sdk/exceptions.py +180 -0
- megaplan_sdk/helpers.py +108 -0
- megaplan_sdk/http_client.py +390 -0
- megaplan_sdk/logging_config.py +53 -0
- megaplan_sdk/models/__init__.py +22 -0
- megaplan_sdk/models/base.py +16 -0
- megaplan_sdk/models/comment.py +58 -0
- megaplan_sdk/models/common.py +107 -0
- megaplan_sdk/models/contractor.py +137 -0
- megaplan_sdk/models/deal.py +96 -0
- megaplan_sdk/models/department.py +40 -0
- megaplan_sdk/models/employee.py +117 -0
- megaplan_sdk/models/project.py +76 -0
- megaplan_sdk/models/task.py +75 -0
- megaplan_sdk/resources/__init__.py +15 -0
- megaplan_sdk/resources/auth.py +73 -0
- megaplan_sdk/resources/base.py +794 -0
- megaplan_sdk/resources/comments.py +148 -0
- megaplan_sdk/resources/contractors.py +173 -0
- megaplan_sdk/resources/deals.py +625 -0
- megaplan_sdk/resources/departments.py +70 -0
- megaplan_sdk/resources/employees.py +216 -0
- megaplan_sdk/resources/full_details.py +143 -0
- megaplan_sdk/resources/projects.py +854 -0
- megaplan_sdk/resources/tasks.py +932 -0
- megaplan_sdk/types.py +56 -0
- megaplan_sdk-0.1.0.dist-info/METADATA +1383 -0
- megaplan_sdk-0.1.0.dist-info/RECORD +36 -0
- megaplan_sdk-0.1.0.dist-info/WHEEL +5 -0
- megaplan_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
- megaplan_sdk-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
"""Deals resource for Megaplan API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import AsyncIterator
|
|
6
|
+
from typing import Any, overload
|
|
7
|
+
|
|
8
|
+
from megaplan_sdk.constants import ContentType
|
|
9
|
+
from megaplan_sdk.models.comment import Comment
|
|
10
|
+
from megaplan_sdk.models.deal import Deal, DealFullDetails, ProgramState
|
|
11
|
+
from megaplan_sdk.resources.base import BaseResource
|
|
12
|
+
from megaplan_sdk.resources.full_details import FullDetailsMixin, RelatedDataConfig
|
|
13
|
+
from megaplan_sdk.types import FilterType
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DealsResource(BaseResource, FullDetailsMixin):
|
|
17
|
+
"""Resource for working with deals."""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
http_client,
|
|
22
|
+
cache=None,
|
|
23
|
+
default_comments_limit: int | None = None,
|
|
24
|
+
default_history_limit: int | None = None,
|
|
25
|
+
):
|
|
26
|
+
"""Initialize deals resource.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
http_client: HTTP client for making requests.
|
|
30
|
+
cache: Optional entity cache.
|
|
31
|
+
default_comments_limit: Default limit for comments in get_full_details().
|
|
32
|
+
default_history_limit: Default limit for history in get_full_details().
|
|
33
|
+
"""
|
|
34
|
+
super().__init__(
|
|
35
|
+
http_client,
|
|
36
|
+
cache=cache,
|
|
37
|
+
default_comments_limit=default_comments_limit,
|
|
38
|
+
default_history_limit=default_history_limit,
|
|
39
|
+
)
|
|
40
|
+
# Define config after __init__ to avoid circular import
|
|
41
|
+
self._full_details_config = [
|
|
42
|
+
RelatedDataConfig(
|
|
43
|
+
"comments", "include_comments", "get_comments", limit_param="comments_limit"
|
|
44
|
+
),
|
|
45
|
+
RelatedDataConfig(
|
|
46
|
+
"history", "include_history", "get_history", limit_param="history_limit"
|
|
47
|
+
),
|
|
48
|
+
RelatedDataConfig("status_history", "include_status_history", "get_status_history"),
|
|
49
|
+
RelatedDataConfig("auditors", "include_auditors", "get_auditors"),
|
|
50
|
+
RelatedDataConfig(
|
|
51
|
+
"responsible_details",
|
|
52
|
+
"include_responsible_details",
|
|
53
|
+
None,
|
|
54
|
+
entity_field="responsible",
|
|
55
|
+
entity_type="employee",
|
|
56
|
+
),
|
|
57
|
+
RelatedDataConfig(
|
|
58
|
+
"contractor_details",
|
|
59
|
+
"include_contractor_details",
|
|
60
|
+
None,
|
|
61
|
+
entity_field="contractor",
|
|
62
|
+
entity_type="contractor",
|
|
63
|
+
),
|
|
64
|
+
RelatedDataConfig(
|
|
65
|
+
"related_tasks",
|
|
66
|
+
"include_related_tasks",
|
|
67
|
+
None,
|
|
68
|
+
custom_fetcher=self._fetch_related_tasks,
|
|
69
|
+
),
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
async def _fetch_related_tasks(self, deal_id: int, **kwargs: Any) -> Any:
|
|
73
|
+
"""Custom fetcher for related tasks.
|
|
74
|
+
|
|
75
|
+
Note: For tasks API, filter config must be serialized to JSON string
|
|
76
|
+
when it's a dict, because API expects filter as string in query params.
|
|
77
|
+
"""
|
|
78
|
+
import json
|
|
79
|
+
from megaplan_sdk.resources.tasks import TasksResource
|
|
80
|
+
|
|
81
|
+
tasks_resource = TasksResource(self._http, cache=self._cache)
|
|
82
|
+
# For tasks API, baseOn must be inside filter config
|
|
83
|
+
# Filter must be JSON string when it's a dict (API requirement)
|
|
84
|
+
filter_config = {"baseOn": {"contentType": ContentType.DEAL, "id": deal_id}}
|
|
85
|
+
# Serialize filter dict to JSON string - API expects string, not object
|
|
86
|
+
filter_str = json.dumps(filter_config, ensure_ascii=False)
|
|
87
|
+
return await tasks_resource.list(filter=filter_str)
|
|
88
|
+
|
|
89
|
+
async def create(self, deal_data: dict[str, Any]) -> Deal:
|
|
90
|
+
"""Create a new deal.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
deal_data: Deal data dictionary.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Created deal.
|
|
97
|
+
"""
|
|
98
|
+
return await self._create_entity("deal", deal_data, Deal)
|
|
99
|
+
|
|
100
|
+
@overload
|
|
101
|
+
async def list(
|
|
102
|
+
self,
|
|
103
|
+
*,
|
|
104
|
+
filter: FilterType | None = None,
|
|
105
|
+
status: ProgramState | None = None,
|
|
106
|
+
q: str | None = None,
|
|
107
|
+
base_on: dict[str, Any] | None = None,
|
|
108
|
+
limit: int | None = None,
|
|
109
|
+
page_after: dict[str, Any] | None = None,
|
|
110
|
+
page_before: dict[str, Any] | None = None,
|
|
111
|
+
page_with: dict[str, Any] | None = None,
|
|
112
|
+
fields: Any | None = None,
|
|
113
|
+
sort_by: list[dict[str, str]] | None = None,
|
|
114
|
+
only_requested_fields: bool | None = None,
|
|
115
|
+
expand: None = None,
|
|
116
|
+
) -> list[Deal]: ...
|
|
117
|
+
|
|
118
|
+
@overload
|
|
119
|
+
async def list(
|
|
120
|
+
self,
|
|
121
|
+
*,
|
|
122
|
+
filter: FilterType | None = None,
|
|
123
|
+
status: ProgramState | None = None,
|
|
124
|
+
q: str | None = None,
|
|
125
|
+
base_on: dict[str, Any] | None = None,
|
|
126
|
+
limit: int | None = None,
|
|
127
|
+
page_after: dict[str, Any] | None = None,
|
|
128
|
+
page_before: dict[str, Any] | None = None,
|
|
129
|
+
page_with: dict[str, Any] | None = None,
|
|
130
|
+
fields: Any | None = None,
|
|
131
|
+
sort_by: list[dict[str, str]] | None = None,
|
|
132
|
+
only_requested_fields: bool | None = None,
|
|
133
|
+
expand: list[str],
|
|
134
|
+
) -> list[DealFullDetails]: ...
|
|
135
|
+
|
|
136
|
+
async def list(
|
|
137
|
+
self,
|
|
138
|
+
filter: FilterType | None = None,
|
|
139
|
+
status: ProgramState | None = None,
|
|
140
|
+
q: str | None = None,
|
|
141
|
+
base_on: dict[str, Any] | None = None,
|
|
142
|
+
limit: int | None = None,
|
|
143
|
+
page_after: dict[str, Any] | None = None,
|
|
144
|
+
page_before: dict[str, Any] | None = None,
|
|
145
|
+
page_with: dict[str, Any] | None = None,
|
|
146
|
+
fields: Any | None = None,
|
|
147
|
+
sort_by: list[dict[str, str]] | None = None,
|
|
148
|
+
only_requested_fields: bool | None = None,
|
|
149
|
+
expand: list[str] | None = None,
|
|
150
|
+
) -> list[Deal] | list[DealFullDetails]:
|
|
151
|
+
"""Get list of deals.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
filter: Trade filter (ID or config).
|
|
155
|
+
status: Program state to filter by.
|
|
156
|
+
q: Search query.
|
|
157
|
+
base_on: Base entity for filtering.
|
|
158
|
+
limit: Number of items per page.
|
|
159
|
+
page_after: Load page starting from this entity.
|
|
160
|
+
page_before: Load page strictly before this entity.
|
|
161
|
+
page_with: Load page containing this entity.
|
|
162
|
+
fields: Additional fields to include.
|
|
163
|
+
sort_by: Sort fields.
|
|
164
|
+
only_requested_fields: Return only requested fields.
|
|
165
|
+
expand: List of fields to expand (e.g., ["responsible", "contractor"]).
|
|
166
|
+
Supported values: "responsible", "contractor".
|
|
167
|
+
If provided, returns list[DealFullDetails] instead of list[Deal].
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
List of deals (list[Deal] if expand is None, list[DealFullDetails] otherwise).
|
|
171
|
+
|
|
172
|
+
Examples:
|
|
173
|
+
>>> # Get deals without expansion
|
|
174
|
+
>>> deals = await client.deals.list(limit=10)
|
|
175
|
+
>>>
|
|
176
|
+
>>> # Get deals with expanded responsible and contractor
|
|
177
|
+
>>> deals_full = await client.deals.list(
|
|
178
|
+
... limit=10, expand=["responsible", "contractor"]
|
|
179
|
+
... )
|
|
180
|
+
>>> for deal_full in deals_full:
|
|
181
|
+
... if deal_full.responsible_details:
|
|
182
|
+
... print(deal_full.responsible_details.display_name())
|
|
183
|
+
... if deal_full.contractor_details:
|
|
184
|
+
... print(deal_full.contractor_details.display_name())
|
|
185
|
+
"""
|
|
186
|
+
path = self._build_path("api", "v3", "deal")
|
|
187
|
+
|
|
188
|
+
# Prepare deal-specific parameters
|
|
189
|
+
extra_params: dict[str, Any] = {}
|
|
190
|
+
if status:
|
|
191
|
+
extra_params["status"] = (
|
|
192
|
+
status.model_dump(by_alias=True) if hasattr(status, "model_dump") else status
|
|
193
|
+
)
|
|
194
|
+
if q:
|
|
195
|
+
extra_params["q"] = q
|
|
196
|
+
if base_on:
|
|
197
|
+
extra_params["baseOn"] = base_on
|
|
198
|
+
|
|
199
|
+
# Use base method to build params (DRY)
|
|
200
|
+
params = self._build_list_params(
|
|
201
|
+
filter=filter,
|
|
202
|
+
limit=limit,
|
|
203
|
+
page_after=page_after,
|
|
204
|
+
page_before=page_before,
|
|
205
|
+
page_with=page_with,
|
|
206
|
+
fields=fields,
|
|
207
|
+
sort_by=sort_by,
|
|
208
|
+
only_requested_fields=only_requested_fields,
|
|
209
|
+
**extra_params,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# 1. Fetch deals
|
|
213
|
+
deals = await self._get_list(path, Deal, params)
|
|
214
|
+
|
|
215
|
+
# 2. If no expand, return as is
|
|
216
|
+
if not expand or not deals:
|
|
217
|
+
return deals
|
|
218
|
+
|
|
219
|
+
# 3. Batch load related entities
|
|
220
|
+
from megaplan_sdk.models.contractor import Contractor
|
|
221
|
+
from megaplan_sdk.models.employee import Employee
|
|
222
|
+
|
|
223
|
+
expand_config: dict[str, tuple[str, type, str]] = {
|
|
224
|
+
"responsible": ("employee", Employee, ContentType.EMPLOYEE),
|
|
225
|
+
"contractor": ("contractor", Contractor, ContentType.CONTRACTOR),
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
expanded = await self._expand_list_entities(deals, expand, expand_config)
|
|
229
|
+
responsible_map = expanded.get("responsible", {})
|
|
230
|
+
contractor_map = expanded.get("contractor", {})
|
|
231
|
+
|
|
232
|
+
# 4. Build DealFullDetails objects
|
|
233
|
+
results = []
|
|
234
|
+
for deal in deals:
|
|
235
|
+
resp_details = None
|
|
236
|
+
contr_details = None
|
|
237
|
+
|
|
238
|
+
if deal.responsible and deal.responsible.id in responsible_map:
|
|
239
|
+
resp_details = responsible_map[deal.responsible.id]
|
|
240
|
+
|
|
241
|
+
if deal.contractor and deal.contractor.id in contractor_map:
|
|
242
|
+
contr_details = contractor_map[deal.contractor.id]
|
|
243
|
+
|
|
244
|
+
results.append(
|
|
245
|
+
DealFullDetails(
|
|
246
|
+
deal=deal,
|
|
247
|
+
responsible_details=resp_details,
|
|
248
|
+
contractor_details=contr_details,
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
return results
|
|
253
|
+
|
|
254
|
+
async def get(self, deal_id: int) -> Deal:
|
|
255
|
+
"""Get deal by ID.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
deal_id: Deal identifier.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Deal details.
|
|
262
|
+
"""
|
|
263
|
+
return await self._get_entity("deal", deal_id, Deal)
|
|
264
|
+
|
|
265
|
+
async def update(self, deal_id: int, deal_data: dict[str, Any]) -> Deal:
|
|
266
|
+
"""Update deal.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
deal_id: Deal identifier.
|
|
270
|
+
deal_data: Updated deal data.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Updated deal.
|
|
274
|
+
"""
|
|
275
|
+
return await self._update_entity("deal", deal_id, deal_data, Deal)
|
|
276
|
+
|
|
277
|
+
async def delete(self, deal_id: int) -> None:
|
|
278
|
+
"""Delete deal.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
deal_id: Deal identifier.
|
|
282
|
+
"""
|
|
283
|
+
await self._delete_entity("deal", deal_id)
|
|
284
|
+
|
|
285
|
+
async def apply_transition(self, deal_id: int, transition_id: int) -> Deal:
|
|
286
|
+
"""Apply transition to deal (change status).
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
deal_id: Deal identifier.
|
|
290
|
+
transition_id: Transition identifier.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
Updated deal.
|
|
294
|
+
"""
|
|
295
|
+
path = self._build_path("api", "v3", "deal", str(deal_id), "applyTransition")
|
|
296
|
+
response = await self._http.post(path, json_data={"transition": transition_id})
|
|
297
|
+
return Deal(**response["data"])
|
|
298
|
+
|
|
299
|
+
async def apply_trigger(self, deal_id: int, trigger_id: int) -> Deal:
|
|
300
|
+
"""Apply trigger to deal.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
deal_id: Deal identifier.
|
|
304
|
+
trigger_id: Trigger identifier.
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
Updated deal.
|
|
308
|
+
"""
|
|
309
|
+
path = self._build_path("api", "v3", "deal", str(deal_id), "applyTrigger")
|
|
310
|
+
response = await self._http.post(path, json_data={"trigger": trigger_id})
|
|
311
|
+
return Deal(**response["data"])
|
|
312
|
+
|
|
313
|
+
async def get_status_history(self, deal_id: int) -> list[dict[str, Any]]:
|
|
314
|
+
"""Get status change history for deal.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
deal_id: Deal identifier.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
List of status history entries.
|
|
321
|
+
"""
|
|
322
|
+
path = self._build_path("api", "v3", "deal", str(deal_id), "statusHistory")
|
|
323
|
+
response = await self._http.get(path)
|
|
324
|
+
data: list[dict[str, Any]] = response.get("data", [])
|
|
325
|
+
return data
|
|
326
|
+
|
|
327
|
+
async def check_exists(self, deal_params: dict[str, Any]) -> bool:
|
|
328
|
+
"""Check if deal exists.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
deal_params: Parameters to check.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
True if deal exists, False otherwise.
|
|
335
|
+
"""
|
|
336
|
+
path = self._build_path("api", "v3", "deal", "checkDealExist")
|
|
337
|
+
response = await self._http.post(path, json_data=deal_params)
|
|
338
|
+
data: dict[str, Any] = response.get("data", {})
|
|
339
|
+
exists: bool = data.get("exists", False)
|
|
340
|
+
return exists
|
|
341
|
+
|
|
342
|
+
async def iterate(
|
|
343
|
+
self,
|
|
344
|
+
limit: int = 100,
|
|
345
|
+
**kwargs: Any,
|
|
346
|
+
) -> AsyncIterator[Deal]:
|
|
347
|
+
"""Iterate over all deals with automatic pagination.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
limit: Number of items per page.
|
|
351
|
+
**kwargs: Additional parameters to pass to list().
|
|
352
|
+
|
|
353
|
+
Yields:
|
|
354
|
+
Deal objects.
|
|
355
|
+
"""
|
|
356
|
+
deal: Deal
|
|
357
|
+
async for deal in self._iterate_generic( # type: ignore[valid-type]
|
|
358
|
+
ContentType.DEAL,
|
|
359
|
+
self.list,
|
|
360
|
+
limit,
|
|
361
|
+
**kwargs,
|
|
362
|
+
):
|
|
363
|
+
yield deal
|
|
364
|
+
|
|
365
|
+
async def get_comments(
|
|
366
|
+
self,
|
|
367
|
+
deal_id: int,
|
|
368
|
+
limit: int | None = None,
|
|
369
|
+
page_after: dict[str, Any] | None = None,
|
|
370
|
+
page_before: dict[str, Any] | None = None,
|
|
371
|
+
page_with: dict[str, Any] | None = None,
|
|
372
|
+
) -> list[Comment]:
|
|
373
|
+
"""Get comments for a deal.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
deal_id: Deal identifier.
|
|
377
|
+
limit: Number of items per page.
|
|
378
|
+
page_after: Load page starting from this entity.
|
|
379
|
+
page_before: Load page strictly before this entity.
|
|
380
|
+
page_with: Load page containing this entity.
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
List of comments.
|
|
384
|
+
|
|
385
|
+
Examples:
|
|
386
|
+
>>> comments = await client.deals.get_comments(deal_id=123)
|
|
387
|
+
"""
|
|
388
|
+
return await self._get_entity_comments(
|
|
389
|
+
"deal",
|
|
390
|
+
deal_id,
|
|
391
|
+
limit,
|
|
392
|
+
page_after,
|
|
393
|
+
page_before,
|
|
394
|
+
page_with,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
async def create_comment(
|
|
398
|
+
self,
|
|
399
|
+
deal_id: int,
|
|
400
|
+
text: str,
|
|
401
|
+
attaches: list[dict[str, Any]] | None = None,
|
|
402
|
+
) -> Comment:
|
|
403
|
+
"""Create a comment for a deal.
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
deal_id: Deal identifier.
|
|
407
|
+
text: Comment text.
|
|
408
|
+
attaches: List of file attachments.
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
Created comment.
|
|
412
|
+
|
|
413
|
+
Examples:
|
|
414
|
+
>>> comment = await client.deals.create_comment(
|
|
415
|
+
... deal_id=123,
|
|
416
|
+
... text="Deal update"
|
|
417
|
+
... )
|
|
418
|
+
"""
|
|
419
|
+
return await self._create_entity_comment(
|
|
420
|
+
"deal",
|
|
421
|
+
deal_id,
|
|
422
|
+
text,
|
|
423
|
+
attaches,
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
async def get_auditors(
|
|
427
|
+
self,
|
|
428
|
+
deal_id: int,
|
|
429
|
+
limit: int | None = None,
|
|
430
|
+
page_after: dict[str, Any] | None = None,
|
|
431
|
+
page_before: dict[str, Any] | None = None,
|
|
432
|
+
page_with: dict[str, Any] | None = None,
|
|
433
|
+
) -> list[dict[str, Any]]:
|
|
434
|
+
"""Get auditors for a deal.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
deal_id: Deal identifier.
|
|
438
|
+
limit: Number of items per page.
|
|
439
|
+
page_after: Load page starting from this entity.
|
|
440
|
+
page_before: Load page strictly before this entity.
|
|
441
|
+
page_with: Load page containing this entity.
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
List of auditors.
|
|
445
|
+
|
|
446
|
+
Examples:
|
|
447
|
+
>>> auditors = await client.deals.get_auditors(deal_id=123)
|
|
448
|
+
"""
|
|
449
|
+
path = self._build_path("api", "v3", "deal", str(deal_id), "auditors")
|
|
450
|
+
|
|
451
|
+
params = self._build_list_params(
|
|
452
|
+
limit=limit,
|
|
453
|
+
page_after=page_after,
|
|
454
|
+
page_before=page_before,
|
|
455
|
+
page_with=page_with,
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
response = await self._http.get(path, params=params if params else None)
|
|
459
|
+
data: list[dict[str, Any]] = response.get("data", [])
|
|
460
|
+
return data
|
|
461
|
+
|
|
462
|
+
async def add_auditor(
|
|
463
|
+
self,
|
|
464
|
+
deal_id: int,
|
|
465
|
+
auditor_id: int,
|
|
466
|
+
) -> dict[str, Any]:
|
|
467
|
+
"""Add auditor to the deal.
|
|
468
|
+
|
|
469
|
+
Args:
|
|
470
|
+
deal_id: Deal identifier.
|
|
471
|
+
auditor_id: Auditor ID (Employee ID).
|
|
472
|
+
|
|
473
|
+
Returns:
|
|
474
|
+
Added auditor.
|
|
475
|
+
|
|
476
|
+
Examples:
|
|
477
|
+
>>> auditor = await client.deals.add_auditor(
|
|
478
|
+
... deal_id=123,
|
|
479
|
+
... auditor_id=456
|
|
480
|
+
... )
|
|
481
|
+
"""
|
|
482
|
+
path = self._build_path("api", "v3", "deal", str(deal_id), "auditors")
|
|
483
|
+
response = await self._http.post(path, json_data={"id": auditor_id})
|
|
484
|
+
result: dict[str, Any] = response.get("data", {})
|
|
485
|
+
return result
|
|
486
|
+
|
|
487
|
+
async def remove_auditor(
|
|
488
|
+
self,
|
|
489
|
+
deal_id: int,
|
|
490
|
+
auditor_id: int,
|
|
491
|
+
) -> None:
|
|
492
|
+
"""Remove auditor from the deal.
|
|
493
|
+
|
|
494
|
+
Args:
|
|
495
|
+
deal_id: Deal identifier.
|
|
496
|
+
auditor_id: Auditor ID.
|
|
497
|
+
|
|
498
|
+
Examples:
|
|
499
|
+
>>> await client.deals.remove_auditor(deal_id=123, auditor_id=456)
|
|
500
|
+
"""
|
|
501
|
+
path = self._build_path("api", "v3", "deal", str(deal_id), "auditors", str(auditor_id))
|
|
502
|
+
await self._http.delete(path)
|
|
503
|
+
|
|
504
|
+
async def get_history(
|
|
505
|
+
self,
|
|
506
|
+
deal_id: int,
|
|
507
|
+
limit: int | None = None,
|
|
508
|
+
page_after: dict[str, Any] | None = None,
|
|
509
|
+
page_before: dict[str, Any] | None = None,
|
|
510
|
+
page_with: dict[str, Any] | None = None,
|
|
511
|
+
) -> list[dict[str, Any]]:
|
|
512
|
+
"""Get history log for a deal.
|
|
513
|
+
|
|
514
|
+
Args:
|
|
515
|
+
deal_id: Deal identifier.
|
|
516
|
+
limit: Number of items per page.
|
|
517
|
+
page_after: Load page starting from this entity.
|
|
518
|
+
page_before: Load page strictly before this entity.
|
|
519
|
+
page_with: Load page containing this entity.
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
List of history entries.
|
|
523
|
+
|
|
524
|
+
Examples:
|
|
525
|
+
>>> history = await client.deals.get_history(deal_id=123, limit=10)
|
|
526
|
+
"""
|
|
527
|
+
return await self._get_entity_history(
|
|
528
|
+
"deal", deal_id, limit, page_after, page_before, page_with
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
async def search_history(
|
|
532
|
+
self,
|
|
533
|
+
deal_id: int,
|
|
534
|
+
query: str,
|
|
535
|
+
limit: int | None = None,
|
|
536
|
+
page_after: dict[str, Any] | None = None,
|
|
537
|
+
page_before: dict[str, Any] | None = None,
|
|
538
|
+
page_with: dict[str, Any] | None = None,
|
|
539
|
+
) -> list[dict[str, Any]]:
|
|
540
|
+
"""Search in deal history log.
|
|
541
|
+
|
|
542
|
+
Args:
|
|
543
|
+
deal_id: Deal identifier.
|
|
544
|
+
query: Search query.
|
|
545
|
+
limit: Number of items per page.
|
|
546
|
+
page_after: Load page starting from this entity.
|
|
547
|
+
page_before: Load page strictly before this entity.
|
|
548
|
+
page_with: Load page containing this entity.
|
|
549
|
+
|
|
550
|
+
Returns:
|
|
551
|
+
List of matching history entries.
|
|
552
|
+
|
|
553
|
+
Examples:
|
|
554
|
+
>>> results = await client.deals.search_history(deal_id=123, query="transition")
|
|
555
|
+
"""
|
|
556
|
+
return await self._search_entity_history(
|
|
557
|
+
"deal", deal_id, query, limit, page_after, page_before, page_with
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
async def get_full_details(
|
|
561
|
+
self,
|
|
562
|
+
deal_id: int,
|
|
563
|
+
include_comments: bool = False,
|
|
564
|
+
include_history: bool = False,
|
|
565
|
+
include_status_history: bool = False,
|
|
566
|
+
include_auditors: bool = False,
|
|
567
|
+
include_responsible_details: bool = False,
|
|
568
|
+
include_contractor_details: bool = False,
|
|
569
|
+
include_related_tasks: bool = False,
|
|
570
|
+
comments_limit: int | None = None,
|
|
571
|
+
history_limit: int | None = None,
|
|
572
|
+
) -> DealFullDetails:
|
|
573
|
+
"""Get full deal details with related entities.
|
|
574
|
+
|
|
575
|
+
This method fetches the deal and optionally loads related data in parallel
|
|
576
|
+
for better performance.
|
|
577
|
+
|
|
578
|
+
Args:
|
|
579
|
+
deal_id: Deal identifier.
|
|
580
|
+
include_comments: Load deal comments.
|
|
581
|
+
include_history: Load change history.
|
|
582
|
+
include_status_history: Load status change history.
|
|
583
|
+
include_auditors: Load auditors list.
|
|
584
|
+
include_responsible_details: Load full responsible (Employee) details.
|
|
585
|
+
include_contractor_details: Load full contractor details.
|
|
586
|
+
include_related_tasks: Load tasks related to this deal.
|
|
587
|
+
comments_limit: Limit for comments (if included).
|
|
588
|
+
None = use global default (from MegaplanClient) or API default.
|
|
589
|
+
Explicit value overrides global default.
|
|
590
|
+
Example: comments_limit=50 returns max 50 comments.
|
|
591
|
+
history_limit: Limit for history (if included).
|
|
592
|
+
None = use global default (from MegaplanClient) or API default.
|
|
593
|
+
Explicit value overrides global default.
|
|
594
|
+
Example: history_limit=100 returns max 100 history entries.
|
|
595
|
+
|
|
596
|
+
Returns:
|
|
597
|
+
DealFullDetails object with all requested data.
|
|
598
|
+
|
|
599
|
+
Examples:
|
|
600
|
+
>>> # Get deal with comments and history
|
|
601
|
+
>>> details = await client.deals.get_full_details(
|
|
602
|
+
... deal_id=123,
|
|
603
|
+
... include_comments=True,
|
|
604
|
+
... include_history=True,
|
|
605
|
+
... comments_limit=50
|
|
606
|
+
... )
|
|
607
|
+
>>> print(details.deal.name)
|
|
608
|
+
>>> print(len(details.comments))
|
|
609
|
+
"""
|
|
610
|
+
return await self._get_full_details_generic(
|
|
611
|
+
entity_id=deal_id,
|
|
612
|
+
entity_getter="get",
|
|
613
|
+
full_details_class=DealFullDetails,
|
|
614
|
+
config=self._full_details_config,
|
|
615
|
+
main_entity_field="deal",
|
|
616
|
+
include_comments=include_comments,
|
|
617
|
+
include_history=include_history,
|
|
618
|
+
include_status_history=include_status_history,
|
|
619
|
+
include_auditors=include_auditors,
|
|
620
|
+
include_responsible_details=include_responsible_details,
|
|
621
|
+
include_contractor_details=include_contractor_details,
|
|
622
|
+
include_related_tasks=include_related_tasks,
|
|
623
|
+
comments_limit=comments_limit,
|
|
624
|
+
history_limit=history_limit,
|
|
625
|
+
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Resource for working with departments."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from megaplan_sdk.models.department import Department
|
|
6
|
+
from megaplan_sdk.resources.base import BaseResource
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DepartmentsResource(BaseResource):
|
|
10
|
+
"""Resource for working with departments.
|
|
11
|
+
|
|
12
|
+
Provides methods to list and get department information.
|
|
13
|
+
|
|
14
|
+
Examples:
|
|
15
|
+
>>> async with MegaplanClient(...) as client:
|
|
16
|
+
... # List all departments
|
|
17
|
+
... departments = await client.departments.list()
|
|
18
|
+
...
|
|
19
|
+
... # Get specific department
|
|
20
|
+
... dept = await client.departments.get(department_id=1)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
async def list(
|
|
24
|
+
self,
|
|
25
|
+
limit: int | None = None,
|
|
26
|
+
page_after: dict[str, Any] | None = None,
|
|
27
|
+
page_before: dict[str, Any] | None = None,
|
|
28
|
+
page_with: dict[str, Any] | None = None,
|
|
29
|
+
) -> list[Department]:
|
|
30
|
+
"""Get list of departments.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
limit: Maximum number of departments to return.
|
|
34
|
+
page_after: Load page starting from this entity.
|
|
35
|
+
page_before: Load page strictly before this entity.
|
|
36
|
+
page_with: Load page containing this entity.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
List of departments.
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
>>> async with MegaplanClient(...) as client:
|
|
43
|
+
... departments = await client.departments.list(limit=50)
|
|
44
|
+
... for dept in departments:
|
|
45
|
+
... print(f"{dept.name}")
|
|
46
|
+
"""
|
|
47
|
+
path = self._build_path("api", "v3", "department")
|
|
48
|
+
params = self._build_list_params(
|
|
49
|
+
limit=limit,
|
|
50
|
+
page_after=page_after,
|
|
51
|
+
page_before=page_before,
|
|
52
|
+
page_with=page_with,
|
|
53
|
+
)
|
|
54
|
+
return await self._get_list(path, Department, params)
|
|
55
|
+
|
|
56
|
+
async def get(self, department_id: int) -> Department:
|
|
57
|
+
"""Get department by ID.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
department_id: Department identifier.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Department instance.
|
|
64
|
+
|
|
65
|
+
Examples:
|
|
66
|
+
>>> async with MegaplanClient(...) as client:
|
|
67
|
+
... dept = await client.departments.get(1)
|
|
68
|
+
... print(dept.name)
|
|
69
|
+
"""
|
|
70
|
+
return await self._get_entity("department", department_id, Department)
|