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,53 @@
|
|
|
1
|
+
"""Logging configuration for Megaplan SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
# Create logger for the SDK
|
|
9
|
+
logger = logging.getLogger("megaplan_sdk")
|
|
10
|
+
logger.setLevel(logging.WARNING)
|
|
11
|
+
|
|
12
|
+
# Create formatter
|
|
13
|
+
formatter = logging.Formatter(
|
|
14
|
+
fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
15
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Create console handler
|
|
19
|
+
console_handler = logging.StreamHandler()
|
|
20
|
+
console_handler.setFormatter(formatter)
|
|
21
|
+
logger.addHandler(console_handler)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def setup_logging(level: str = "WARNING") -> None:
|
|
25
|
+
"""Setup logging for SDK.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL).
|
|
29
|
+
"""
|
|
30
|
+
logger.setLevel(getattr(logging, level.upper()))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def sanitize_dict(data: dict[str, Any]) -> dict[str, Any]:
|
|
34
|
+
"""Remove sensitive data from dict for logging.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
data: Dictionary to sanitize.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Sanitized dictionary with sensitive values redacted.
|
|
41
|
+
"""
|
|
42
|
+
sensitive_keys = {
|
|
43
|
+
"password",
|
|
44
|
+
"access_token",
|
|
45
|
+
"refresh_token",
|
|
46
|
+
"Authorization",
|
|
47
|
+
"token",
|
|
48
|
+
"secret",
|
|
49
|
+
"api_key",
|
|
50
|
+
"apiKey",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {k: "***REDACTED***" if k in sensitive_keys else v for k, v in data.items()}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Models for Megaplan SDK."""
|
|
2
|
+
|
|
3
|
+
from megaplan_sdk.models.base import BaseEntity
|
|
4
|
+
from megaplan_sdk.models.common import File, Meta, Pagination, SortField
|
|
5
|
+
from megaplan_sdk.models.deal import Deal, ProgramState, TradeFilter
|
|
6
|
+
from megaplan_sdk.models.project import Project, ProjectFilter
|
|
7
|
+
from megaplan_sdk.models.task import Task, TaskFilter
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"BaseEntity",
|
|
11
|
+
"Meta",
|
|
12
|
+
"Pagination",
|
|
13
|
+
"SortField",
|
|
14
|
+
"File",
|
|
15
|
+
"Task",
|
|
16
|
+
"TaskFilter",
|
|
17
|
+
"Project",
|
|
18
|
+
"ProjectFilter",
|
|
19
|
+
"Deal",
|
|
20
|
+
"TradeFilter",
|
|
21
|
+
"ProgramState",
|
|
22
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
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 and id.
|
|
8
|
+
|
|
9
|
+
All Megaplan entities have contentType and id fields.
|
|
10
|
+
Link entities (for references) only contain these two fields.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
content_type: str = Field(alias="contentType")
|
|
14
|
+
id: int
|
|
15
|
+
|
|
16
|
+
model_config = ConfigDict(populate_by_name=True, extra="ignore")
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Comment model for Megaplan SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
from megaplan_sdk.models.base import BaseEntity
|
|
8
|
+
from megaplan_sdk.models.common import TimestampMixin
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Comment(BaseEntity, TimestampMixin):
|
|
12
|
+
"""Comment entity.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
id: Comment identifier.
|
|
16
|
+
content_type: Entity content type (always "Comment").
|
|
17
|
+
subject: Parent entity (task, project, deal, etc.).
|
|
18
|
+
owner: Comment author (Employee, ContractorCompany, or ContractorHuman).
|
|
19
|
+
content: Comment text content.
|
|
20
|
+
attaches: List of attached files.
|
|
21
|
+
attaches_count: Number of attachments.
|
|
22
|
+
work_time: Work time interval (DateInterval entity with contentType, value).
|
|
23
|
+
work_date: Work date (DateTime entity with contentType, value, timestamp).
|
|
24
|
+
is_unread: Whether comment is unread.
|
|
25
|
+
is_dropped: Whether comment is deleted.
|
|
26
|
+
completed: Completion status.
|
|
27
|
+
created_at: Creation timestamp (timeCreated).
|
|
28
|
+
updated_at: Last update timestamp (timeUpdated).
|
|
29
|
+
|
|
30
|
+
Legacy fields for compatibility:
|
|
31
|
+
author: Alias for owner.
|
|
32
|
+
text: Alias for content.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
content_type: str = Field(alias="contentType", default="Comment")
|
|
36
|
+
subject: BaseEntity | None = None
|
|
37
|
+
owner: BaseEntity | None = None
|
|
38
|
+
content: str | None = None
|
|
39
|
+
attaches: list[BaseEntity] | None = None
|
|
40
|
+
attaches_count: int | None = Field(alias="attachesCount", default=None)
|
|
41
|
+
work_time: dict[str, Any] | None = Field(alias="workTime", default=None)
|
|
42
|
+
work_date: dict[str, Any] | None = Field(alias="workDate", default=None)
|
|
43
|
+
is_unread: bool | None = Field(alias="isUnread", default=None)
|
|
44
|
+
is_dropped: bool | None = Field(alias="isDropped", default=None)
|
|
45
|
+
completed: int | None = None
|
|
46
|
+
|
|
47
|
+
# Legacy aliases for compatibility
|
|
48
|
+
@property
|
|
49
|
+
def author(self) -> BaseEntity | None:
|
|
50
|
+
"""Alias for owner field."""
|
|
51
|
+
return self.owner
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def text(self) -> str | None:
|
|
55
|
+
"""Alias for content field."""
|
|
56
|
+
return self.content
|
|
57
|
+
|
|
58
|
+
model_config = ConfigDict(populate_by_name=True, extra="ignore")
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Common models for Megaplan SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Generic, TypeVar
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Pagination(BaseModel):
|
|
11
|
+
"""Pagination information from API response.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
count: Total number of items.
|
|
15
|
+
limit: Items per page.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
count: int = 0
|
|
19
|
+
limit: int = 100
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Meta(BaseModel):
|
|
23
|
+
"""Meta information from API response.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
status: HTTP status code.
|
|
27
|
+
errors: List of error details.
|
|
28
|
+
pagination: Pagination information.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
status: int = 200
|
|
32
|
+
errors: list[dict[str, Any]] = Field(default_factory=list)
|
|
33
|
+
pagination: Pagination | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ApiResponse(BaseModel, Generic[T]):
|
|
37
|
+
"""Generic API response wrapper.
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
meta: Meta information (status, errors, pagination).
|
|
41
|
+
data: Response data (single item or list).
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
meta: Meta
|
|
45
|
+
data: T
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class File(BaseModel):
|
|
49
|
+
"""File model for file uploads and attachments.
|
|
50
|
+
|
|
51
|
+
Attributes:
|
|
52
|
+
id: File identifier.
|
|
53
|
+
content_type: File content type.
|
|
54
|
+
path: File path.
|
|
55
|
+
mime_type: MIME type.
|
|
56
|
+
name: File name.
|
|
57
|
+
size: File size in bytes.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
id: int
|
|
61
|
+
content_type: str = Field(alias="contentType", default="File")
|
|
62
|
+
path: str | None = None
|
|
63
|
+
mime_type: str | None = Field(alias="mimeType", default=None)
|
|
64
|
+
name: str | None = None
|
|
65
|
+
size: int | None = None
|
|
66
|
+
|
|
67
|
+
model_config = ConfigDict(populate_by_name=True, extra="ignore")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class DateTime(BaseModel):
|
|
71
|
+
"""DateTime model for date/time fields.
|
|
72
|
+
|
|
73
|
+
Megaplan API returns dates as objects with contentType and value.
|
|
74
|
+
|
|
75
|
+
Attributes:
|
|
76
|
+
content_type: Always "DateTime".
|
|
77
|
+
value: ISO 8601 datetime string.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
content_type: str = Field(alias="contentType", default="DateTime")
|
|
81
|
+
value: str
|
|
82
|
+
|
|
83
|
+
model_config = ConfigDict(populate_by_name=True, extra="ignore")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class SortField(BaseModel):
|
|
87
|
+
"""Sort field for API requests.
|
|
88
|
+
|
|
89
|
+
Attributes:
|
|
90
|
+
field: Field name to sort by.
|
|
91
|
+
direction: Sort direction (asc or desc).
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
field: str
|
|
95
|
+
direction: str = "asc" # asc or desc
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class TimestampMixin(BaseModel):
|
|
99
|
+
"""Mixin for entities with creation and update timestamps.
|
|
100
|
+
|
|
101
|
+
Attributes:
|
|
102
|
+
created_at: Entity creation timestamp.
|
|
103
|
+
updated_at: Entity last update timestamp.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
created_at: str | DateTime | None = Field(alias="createdAt", default=None)
|
|
107
|
+
updated_at: str | DateTime | None = Field(alias="updatedAt", default=None)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Contractor models for Megaplan SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
from megaplan_sdk.models.base import BaseEntity
|
|
8
|
+
from megaplan_sdk.models.common import TimestampMixin
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Contractor(BaseEntity, TimestampMixin):
|
|
12
|
+
"""Base contractor entity.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
id: Contractor identifier.
|
|
16
|
+
content_type: Entity content type (Contractor, ContractorCompany, ContractorHuman).
|
|
17
|
+
name: Contractor name.
|
|
18
|
+
email: Email address.
|
|
19
|
+
phone: Phone number.
|
|
20
|
+
site: Website URL.
|
|
21
|
+
description: Contractor description.
|
|
22
|
+
category: Contractor category.
|
|
23
|
+
manager: Responsible manager.
|
|
24
|
+
created_at: Creation timestamp.
|
|
25
|
+
updated_at: Last update timestamp.
|
|
26
|
+
status: Contractor status.
|
|
27
|
+
tags: List of tags.
|
|
28
|
+
custom_fields: Custom field values.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
content_type: str = Field(alias="contentType", default="Contractor")
|
|
32
|
+
name: str | None = None
|
|
33
|
+
email: str | None = None
|
|
34
|
+
phone: str | None = None
|
|
35
|
+
site: str | None = None
|
|
36
|
+
description: str | None = None
|
|
37
|
+
category: BaseEntity | None = None
|
|
38
|
+
manager: BaseEntity | None = None
|
|
39
|
+
status: str | None = None
|
|
40
|
+
tags: list[BaseEntity | str] | None = None # Can be Tag entities or strings
|
|
41
|
+
custom_fields: dict[str, Any] | None = Field(alias="customFields", default=None)
|
|
42
|
+
|
|
43
|
+
model_config = ConfigDict(populate_by_name=True, extra="ignore")
|
|
44
|
+
|
|
45
|
+
def display_name(self) -> str:
|
|
46
|
+
"""Get display name for contractor.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Contractor name or fallback ID representation.
|
|
50
|
+
|
|
51
|
+
Examples:
|
|
52
|
+
>>> contractor = Contractor(id=1, name="ACME Corp")
|
|
53
|
+
>>> contractor.display_name()
|
|
54
|
+
'ACME Corp'
|
|
55
|
+
"""
|
|
56
|
+
return self.name or f"Contractor#{self.id}"
|
|
57
|
+
|
|
58
|
+
def __str__(self) -> str:
|
|
59
|
+
"""Return display name for string representation.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Display name.
|
|
63
|
+
|
|
64
|
+
Examples:
|
|
65
|
+
>>> contractor = Contractor(id=1, name="ACME Corp")
|
|
66
|
+
>>> str(contractor)
|
|
67
|
+
'ACME Corp'
|
|
68
|
+
"""
|
|
69
|
+
return self.display_name()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ContractorCompany(Contractor):
|
|
73
|
+
"""Company contractor entity.
|
|
74
|
+
|
|
75
|
+
Attributes:
|
|
76
|
+
content_type: Entity content type (always "ContractorCompany").
|
|
77
|
+
inn: Tax identification number (INN).
|
|
78
|
+
kpp: Tax registration reason code (KPP).
|
|
79
|
+
ogrn: Primary state registration number (OGRN).
|
|
80
|
+
legal_name: Legal company name.
|
|
81
|
+
legal_address: Legal address.
|
|
82
|
+
actual_address: Actual address.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
content_type: str = Field(alias="contentType", default="ContractorCompany")
|
|
86
|
+
inn: str | None = None
|
|
87
|
+
kpp: str | None = None
|
|
88
|
+
ogrn: str | None = None
|
|
89
|
+
legal_name: str | None = Field(alias="legalName", default=None)
|
|
90
|
+
legal_address: str | None = Field(alias="legalAddress", default=None)
|
|
91
|
+
actual_address: str | None = Field(alias="actualAddress", default=None)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class ContractorHuman(Contractor):
|
|
95
|
+
"""Human contractor entity (individual).
|
|
96
|
+
|
|
97
|
+
Attributes:
|
|
98
|
+
content_type: Entity content type (always "ContractorHuman").
|
|
99
|
+
first_name: First name.
|
|
100
|
+
middle_name: Middle name.
|
|
101
|
+
last_name: Last name.
|
|
102
|
+
birthday: Birth date.
|
|
103
|
+
passport: Passport information.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
content_type: str = Field(alias="contentType", default="ContractorHuman")
|
|
107
|
+
first_name: str | None = Field(alias="firstName", default=None)
|
|
108
|
+
middle_name: str | None = Field(alias="middleName", default=None)
|
|
109
|
+
last_name: str | None = Field(alias="lastName", default=None)
|
|
110
|
+
birthday: str | None = None
|
|
111
|
+
passport: str | None = None
|
|
112
|
+
|
|
113
|
+
def display_name(self) -> str:
|
|
114
|
+
"""Get display name for human contractor.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Full name or name field or fallback ID representation.
|
|
118
|
+
|
|
119
|
+
Examples:
|
|
120
|
+
>>> human = ContractorHuman(
|
|
121
|
+
... id=1, first_name="John", last_name="Doe"
|
|
122
|
+
... )
|
|
123
|
+
>>> human.display_name()
|
|
124
|
+
'John Doe'
|
|
125
|
+
"""
|
|
126
|
+
# Try to build from first/last name
|
|
127
|
+
parts = []
|
|
128
|
+
if self.first_name:
|
|
129
|
+
parts.append(self.first_name)
|
|
130
|
+
if self.last_name:
|
|
131
|
+
parts.append(self.last_name)
|
|
132
|
+
|
|
133
|
+
if parts:
|
|
134
|
+
return " ".join(parts)
|
|
135
|
+
|
|
136
|
+
# Fallback to name field or ID
|
|
137
|
+
return self.name or f"Contractor#{self.id}"
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Deal models for Megaplan SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
8
|
+
|
|
9
|
+
from megaplan_sdk.models.base import BaseEntity
|
|
10
|
+
from megaplan_sdk.models.common import DateTime, TimestampMixin
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TradeFilter(BaseModel):
|
|
14
|
+
"""Trade filter configuration for deals.
|
|
15
|
+
|
|
16
|
+
Can be used as filter ID (integer) or filter configuration (dict).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
id: int | None = None
|
|
20
|
+
config: dict[str, Any] | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ProgramState(BaseModel):
|
|
24
|
+
"""Program state model.
|
|
25
|
+
|
|
26
|
+
Represents a state in a deal program.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
id: int
|
|
30
|
+
content_type: str = Field(alias="contentType", default="ProgramState")
|
|
31
|
+
name: str | None = None
|
|
32
|
+
program: BaseEntity | None = None
|
|
33
|
+
|
|
34
|
+
model_config = ConfigDict(populate_by_name=True, extra="ignore")
|
|
35
|
+
|
|
36
|
+
def __str__(self) -> str:
|
|
37
|
+
"""Return state name for display.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
State name or fallback ID representation.
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
>>> state = ProgramState(id=1, name="In Progress")
|
|
44
|
+
>>> str(state)
|
|
45
|
+
'In Progress'
|
|
46
|
+
"""
|
|
47
|
+
return self.name or f"State#{self.id}"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Deal(TimestampMixin):
|
|
51
|
+
"""Deal model.
|
|
52
|
+
|
|
53
|
+
Represents a deal in Megaplan with all its properties.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
id: int
|
|
57
|
+
content_type: str = Field(alias="contentType", default="Deal")
|
|
58
|
+
name: str | None = None
|
|
59
|
+
program: BaseEntity | None = None
|
|
60
|
+
state: ProgramState | None = None
|
|
61
|
+
contractor: BaseEntity | None = None
|
|
62
|
+
responsible: BaseEntity | None = None
|
|
63
|
+
sum_base: float | None = Field(alias="sumBase", default=None)
|
|
64
|
+
currency: BaseEntity | None = None
|
|
65
|
+
deadline: str | DateTime | dict[str, Any] | None = None # Can be DateOnly, DateTime, or string
|
|
66
|
+
description: str | None = None
|
|
67
|
+
tags: list[BaseEntity] | None = None
|
|
68
|
+
attaches: list[BaseEntity] | None = None
|
|
69
|
+
|
|
70
|
+
model_config = ConfigDict(populate_by_name=True, extra="ignore")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class DealFullDetails(BaseModel):
|
|
74
|
+
"""Full deal details with all related entities.
|
|
75
|
+
|
|
76
|
+
Attributes:
|
|
77
|
+
deal: Main deal entity.
|
|
78
|
+
comments: List of comments (if requested).
|
|
79
|
+
history: Change history entries (if requested).
|
|
80
|
+
status_history: Status change history (if requested).
|
|
81
|
+
auditors: List of auditors (if requested).
|
|
82
|
+
responsible_details: Full responsible employee details (if requested).
|
|
83
|
+
contractor_details: Full contractor details (if requested).
|
|
84
|
+
related_tasks: Tasks related to this deal (if requested).
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
deal: Deal
|
|
88
|
+
comments: list[Any] | None = None
|
|
89
|
+
history: list[dict[str, Any]] | None = None
|
|
90
|
+
status_history: list[dict[str, Any]] | None = None
|
|
91
|
+
auditors: list[dict[str, Any]] | None = None
|
|
92
|
+
responsible_details: Any | None = None
|
|
93
|
+
contractor_details: Any | None = None
|
|
94
|
+
related_tasks: list[Any] | None = None
|
|
95
|
+
|
|
96
|
+
model_config = ConfigDict(populate_by_name=True, extra="ignore")
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Department model for Megaplan SDK."""
|
|
2
|
+
|
|
3
|
+
from pydantic import ConfigDict, Field
|
|
4
|
+
|
|
5
|
+
from megaplan_sdk.models.base import BaseEntity
|
|
6
|
+
from megaplan_sdk.models.common import TimestampMixin
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Department(BaseEntity, TimestampMixin):
|
|
10
|
+
"""Department entity.
|
|
11
|
+
|
|
12
|
+
Attributes:
|
|
13
|
+
id: Department identifier.
|
|
14
|
+
content_type: Entity content type (always "Department").
|
|
15
|
+
name: Department name.
|
|
16
|
+
parent: Parent department (if nested structure).
|
|
17
|
+
manager: Department manager (Employee reference).
|
|
18
|
+
created_at: Creation timestamp.
|
|
19
|
+
updated_at: Last update timestamp.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
content_type: str = Field(alias="contentType", default="Department")
|
|
23
|
+
name: str | None = None
|
|
24
|
+
parent: BaseEntity | None = None
|
|
25
|
+
manager: BaseEntity | None = None
|
|
26
|
+
|
|
27
|
+
model_config = ConfigDict(populate_by_name=True, extra="ignore")
|
|
28
|
+
|
|
29
|
+
def __str__(self) -> str:
|
|
30
|
+
"""Return department name for display.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Department name or fallback ID representation.
|
|
34
|
+
|
|
35
|
+
Examples:
|
|
36
|
+
>>> dept = Department(id=1, name="Engineering")
|
|
37
|
+
>>> str(dept)
|
|
38
|
+
'Engineering'
|
|
39
|
+
"""
|
|
40
|
+
return self.name or f"Department#{self.id}"
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Employee model for Megaplan SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
from megaplan_sdk.models.base import BaseEntity
|
|
8
|
+
from megaplan_sdk.models.common import DateTime, TimestampMixin
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Employee(BaseEntity, TimestampMixin):
|
|
12
|
+
"""Employee entity.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
id: Employee identifier.
|
|
16
|
+
content_type: Entity content type (always "Employee").
|
|
17
|
+
first_name: First name.
|
|
18
|
+
middle_name: Middle name.
|
|
19
|
+
last_name: Last name.
|
|
20
|
+
email: Email address.
|
|
21
|
+
phone: Phone number.
|
|
22
|
+
position: Job position.
|
|
23
|
+
department: Department entity.
|
|
24
|
+
manager: Direct manager entity.
|
|
25
|
+
birthday: Birth date (DateOnly entity with year, month, day fields).
|
|
26
|
+
hired_at: Hire date.
|
|
27
|
+
fired_at: Termination date.
|
|
28
|
+
status: Employment status (EmployeeStatus entity with name, masterType).
|
|
29
|
+
avatar: Avatar image entity.
|
|
30
|
+
is_admin: Whether employee has admin rights.
|
|
31
|
+
is_client: Whether this is a client account.
|
|
32
|
+
access_role: Access role entity.
|
|
33
|
+
created_at: Creation timestamp.
|
|
34
|
+
updated_at: Last update timestamp.
|
|
35
|
+
custom_fields: Custom field values.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
content_type: str = Field(alias="contentType", default="Employee")
|
|
39
|
+
first_name: str | None = Field(alias="firstName", default=None)
|
|
40
|
+
middle_name: str | None = Field(alias="middleName", default=None)
|
|
41
|
+
last_name: str | None = Field(alias="lastName", default=None)
|
|
42
|
+
email: str | None = None
|
|
43
|
+
phone: str | None = None
|
|
44
|
+
position: str | None = None
|
|
45
|
+
department: BaseEntity | None = None
|
|
46
|
+
manager: BaseEntity | None = None
|
|
47
|
+
birthday: dict[str, Any] | None = None # DateOnly entity (year, month, day)
|
|
48
|
+
hired_at: str | DateTime | None = Field(alias="hiredAt", default=None)
|
|
49
|
+
fired_at: str | DateTime | None = Field(alias="firedAt", default=None)
|
|
50
|
+
status: BaseEntity | None = None # EmployeeStatus entity
|
|
51
|
+
avatar: BaseEntity | None = None
|
|
52
|
+
is_admin: bool | None = Field(alias="isAdmin", default=None)
|
|
53
|
+
is_client: bool | None = Field(alias="isClient", default=None)
|
|
54
|
+
access_role: BaseEntity | None = Field(alias="accessRole", default=None)
|
|
55
|
+
custom_fields: dict[str, Any] | None = Field(alias="customFields", default=None)
|
|
56
|
+
|
|
57
|
+
model_config = ConfigDict(populate_by_name=True, extra="ignore")
|
|
58
|
+
|
|
59
|
+
def full_name(self, include_middle: bool = True) -> str:
|
|
60
|
+
"""Get full name of the employee.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
include_middle: Include middle name (default: True).
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Full name string.
|
|
67
|
+
|
|
68
|
+
Examples:
|
|
69
|
+
>>> employee = Employee(
|
|
70
|
+
... id=1, first_name="John", middle_name="A", last_name="Doe"
|
|
71
|
+
... )
|
|
72
|
+
>>> employee.full_name()
|
|
73
|
+
'John A Doe'
|
|
74
|
+
>>> employee.full_name(include_middle=False)
|
|
75
|
+
'John Doe'
|
|
76
|
+
"""
|
|
77
|
+
parts = []
|
|
78
|
+
if self.first_name:
|
|
79
|
+
parts.append(self.first_name)
|
|
80
|
+
if include_middle and self.middle_name:
|
|
81
|
+
parts.append(self.middle_name)
|
|
82
|
+
if self.last_name:
|
|
83
|
+
parts.append(self.last_name)
|
|
84
|
+
return " ".join(parts) if parts else f"Employee#{self.id}"
|
|
85
|
+
|
|
86
|
+
def display_name(self) -> str:
|
|
87
|
+
"""Get display name with position.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Full name with position in parentheses.
|
|
91
|
+
|
|
92
|
+
Examples:
|
|
93
|
+
>>> employee = Employee(
|
|
94
|
+
... id=1, first_name="John", last_name="Doe", position="CEO"
|
|
95
|
+
... )
|
|
96
|
+
>>> employee.display_name()
|
|
97
|
+
'John Doe (CEO)'
|
|
98
|
+
"""
|
|
99
|
+
name = self.full_name(include_middle=False)
|
|
100
|
+
if self.position:
|
|
101
|
+
return f"{name} ({self.position})"
|
|
102
|
+
return name
|
|
103
|
+
|
|
104
|
+
def __str__(self) -> str:
|
|
105
|
+
"""Return display name for string representation.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Display name with position.
|
|
109
|
+
|
|
110
|
+
Examples:
|
|
111
|
+
>>> employee = Employee(
|
|
112
|
+
... id=1, first_name="John", last_name="Doe", position="CEO"
|
|
113
|
+
... )
|
|
114
|
+
>>> str(employee)
|
|
115
|
+
'John Doe (CEO)'
|
|
116
|
+
"""
|
|
117
|
+
return self.display_name()
|