amsdal_crm 0.1.0__py3-none-any.whl → 0.1.1__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.
- amsdal_crm/Third-Party Materials - AMSDAL Dependencies - License Notices.md +2094 -0
- amsdal_crm/__about__.py +1 -1
- amsdal_crm/app.py +5 -0
- amsdal_crm/lifecycle/consumer.py +181 -0
- {amsdal_crm-0.1.0.dist-info → amsdal_crm-0.1.1.dist-info}/METADATA +1 -1
- {amsdal_crm-0.1.0.dist-info → amsdal_crm-0.1.1.dist-info}/RECORD +7 -6
- {amsdal_crm-0.1.0.dist-info → amsdal_crm-0.1.1.dist-info}/WHEEL +0 -0
amsdal_crm/__about__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '0.1.
|
|
1
|
+
__version__ = '0.1.1'
|
amsdal_crm/app.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""CRM App Configuration."""
|
|
2
2
|
|
|
3
3
|
from amsdal.contrib.app_config import AppConfig
|
|
4
|
+
from amsdal.contrib.frontend_configs.constants import ON_RESPONSE_EVENT
|
|
4
5
|
from amsdal_utils.lifecycle.enum import LifecycleEvent
|
|
5
6
|
from amsdal_utils.lifecycle.producer import LifecycleProducer
|
|
6
7
|
|
|
@@ -12,6 +13,7 @@ class CRMAppConfig(AppConfig):
|
|
|
12
13
|
|
|
13
14
|
def on_ready(self) -> None:
|
|
14
15
|
"""Set up CRM lifecycle listeners and initialize module."""
|
|
16
|
+
from amsdal_crm.lifecycle.consumer import CustomAttributesFrontendConfigConsumer
|
|
15
17
|
from amsdal_crm.lifecycle.consumer import DealWonNotificationConsumer
|
|
16
18
|
from amsdal_crm.lifecycle.consumer import LoadCRMFixturesConsumer
|
|
17
19
|
|
|
@@ -21,5 +23,8 @@ class CRMAppConfig(AppConfig):
|
|
|
21
23
|
# Custom CRM events
|
|
22
24
|
LifecycleProducer.add_listener(CRMLifecycleEvent.ON_DEAL_WON, DealWonNotificationConsumer) # type: ignore[arg-type]
|
|
23
25
|
|
|
26
|
+
# Propagate custom attributes to frontend configs
|
|
27
|
+
LifecycleProducer.add_listener(ON_RESPONSE_EVENT, CustomAttributesFrontendConfigConsumer) # type: ignore[arg-type]
|
|
28
|
+
|
|
24
29
|
# Additional event listeners can be added here
|
|
25
30
|
# LifecycleProducer.add_listener(CRMLifecycleEvent.ON_DEAL_LOST, DealLostNotificationConsumer)
|
amsdal_crm/lifecycle/consumer.py
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
"""CRM Lifecycle Consumers."""
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
|
+
from typing import Any
|
|
4
5
|
|
|
5
6
|
from amsdal_utils.lifecycle.consumer import LifecycleConsumer
|
|
7
|
+
from amsdal_utils.models.data_models.address import Address
|
|
8
|
+
from amsdal_utils.models.enums import Versions
|
|
6
9
|
|
|
7
10
|
if TYPE_CHECKING:
|
|
8
11
|
from amsdal_crm.models.deal import Deal
|
|
9
12
|
|
|
13
|
+
# Entity types that support custom fields
|
|
14
|
+
CRM_ENTITY_TYPES = {'Contact', 'Account', 'Deal'}
|
|
15
|
+
|
|
10
16
|
|
|
11
17
|
class LoadCRMFixturesConsumer(LifecycleConsumer):
|
|
12
18
|
"""Consumer that loads CRM fixtures on server startup."""
|
|
@@ -42,3 +48,178 @@ class DealWonNotificationConsumer(LifecycleConsumer):
|
|
|
42
48
|
async def on_event_async(self, deal: 'Deal', user_email: str) -> None:
|
|
43
49
|
"""Async version of on_event."""
|
|
44
50
|
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _custom_field_def_to_control(field_def: Any) -> dict[str, Any]:
|
|
54
|
+
"""Convert a CustomFieldDefinition to a frontend control config.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
field_def: A CustomFieldDefinition instance with metadata about a custom field
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
A dictionary representing the frontend control configuration
|
|
61
|
+
"""
|
|
62
|
+
control: dict[str, Any] = {
|
|
63
|
+
'name': f'custom_fields.{field_def.field_name}',
|
|
64
|
+
'label': field_def.field_label,
|
|
65
|
+
'required': field_def.is_required,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if field_def.help_text:
|
|
69
|
+
control['description'] = field_def.help_text
|
|
70
|
+
|
|
71
|
+
if field_def.default_value is not None:
|
|
72
|
+
control['value'] = field_def.default_value
|
|
73
|
+
|
|
74
|
+
if field_def.field_type == 'text':
|
|
75
|
+
control['type'] = 'text'
|
|
76
|
+
elif field_def.field_type == 'number':
|
|
77
|
+
control['type'] = 'number'
|
|
78
|
+
elif field_def.field_type == 'date':
|
|
79
|
+
control['type'] = 'date'
|
|
80
|
+
elif field_def.field_type == 'choice':
|
|
81
|
+
control['type'] = 'select'
|
|
82
|
+
if field_def.choices:
|
|
83
|
+
control['options'] = [{'label': choice, 'value': choice} for choice in field_def.choices]
|
|
84
|
+
|
|
85
|
+
return control
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _add_custom_fields_to_control(control: dict[str, Any], custom_field_controls: list[dict[str, Any]]) -> None:
|
|
89
|
+
"""Add custom field controls to the main control configuration.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
control: The main frontend control configuration
|
|
93
|
+
custom_field_controls: List of custom field control configurations
|
|
94
|
+
"""
|
|
95
|
+
if not custom_field_controls:
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
if 'controls' not in control:
|
|
99
|
+
control['controls'] = []
|
|
100
|
+
|
|
101
|
+
# Find the custom_fields control and update it, or append custom fields at the end
|
|
102
|
+
custom_fields_control = None
|
|
103
|
+
for ctrl in control.get('controls', []):
|
|
104
|
+
if ctrl.get('name') == 'custom_fields':
|
|
105
|
+
custom_fields_control = ctrl
|
|
106
|
+
break
|
|
107
|
+
|
|
108
|
+
if custom_fields_control:
|
|
109
|
+
# Update the custom_fields control to be a group with nested controls
|
|
110
|
+
custom_fields_control['type'] = 'group'
|
|
111
|
+
custom_fields_control['controls'] = custom_field_controls
|
|
112
|
+
else:
|
|
113
|
+
# Add custom fields as a new group at the end
|
|
114
|
+
control['controls'].append(
|
|
115
|
+
{
|
|
116
|
+
'type': 'group',
|
|
117
|
+
'name': 'custom_fields',
|
|
118
|
+
'label': 'Custom Fields',
|
|
119
|
+
'controls': custom_field_controls,
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class CustomAttributesFrontendConfigConsumer(LifecycleConsumer):
|
|
125
|
+
"""Consumer that propagates custom attributes into frontend config.
|
|
126
|
+
|
|
127
|
+
This consumer listens to response events and adds custom field definitions
|
|
128
|
+
as frontend controls for CRM entities (Contact, Account, Deal).
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
def on_event(
|
|
132
|
+
self,
|
|
133
|
+
request: Any,
|
|
134
|
+
response: dict[str, Any],
|
|
135
|
+
) -> None:
|
|
136
|
+
"""Handle response event by adding custom field controls.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
request: The request object containing query and path parameters.
|
|
140
|
+
response: The response dictionary to be processed.
|
|
141
|
+
"""
|
|
142
|
+
from amsdal_crm.models.custom_field_definition import CustomFieldDefinition
|
|
143
|
+
|
|
144
|
+
class_name = self._extract_class_name(request)
|
|
145
|
+
|
|
146
|
+
if class_name not in CRM_ENTITY_TYPES:
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
if not isinstance(response, dict) or 'control' not in response:
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
custom_field_defs = list(
|
|
153
|
+
CustomFieldDefinition.objects.filter(
|
|
154
|
+
entity_type=class_name,
|
|
155
|
+
_metadata__is_deleted=False,
|
|
156
|
+
_address__object_version=Versions.LATEST,
|
|
157
|
+
).execute()
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
if not custom_field_defs:
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
# Sort by display_order
|
|
164
|
+
custom_field_defs.sort(key=lambda x: x.display_order)
|
|
165
|
+
|
|
166
|
+
custom_field_controls = [_custom_field_def_to_control(field_def) for field_def in custom_field_defs]
|
|
167
|
+
|
|
168
|
+
_add_custom_fields_to_control(response['control'], custom_field_controls)
|
|
169
|
+
|
|
170
|
+
async def on_event_async(
|
|
171
|
+
self,
|
|
172
|
+
request: Any,
|
|
173
|
+
response: dict[str, Any],
|
|
174
|
+
) -> None:
|
|
175
|
+
"""Async version of on_event.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
request: The request object containing query and path parameters.
|
|
179
|
+
response: The response dictionary to be processed.
|
|
180
|
+
"""
|
|
181
|
+
from amsdal_crm.models.custom_field_definition import CustomFieldDefinition
|
|
182
|
+
|
|
183
|
+
class_name = self._extract_class_name(request)
|
|
184
|
+
|
|
185
|
+
if class_name not in CRM_ENTITY_TYPES:
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
if not isinstance(response, dict) or 'control' not in response:
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
custom_field_defs = list(
|
|
192
|
+
await CustomFieldDefinition.objects.filter(
|
|
193
|
+
entity_type=class_name,
|
|
194
|
+
_metadata__is_deleted=False,
|
|
195
|
+
_address__object_version=Versions.LATEST,
|
|
196
|
+
).aexecute()
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
if not custom_field_defs:
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
# Sort by display_order
|
|
203
|
+
custom_field_defs.sort(key=lambda x: x.display_order)
|
|
204
|
+
|
|
205
|
+
custom_field_controls = [_custom_field_def_to_control(field_def) for field_def in custom_field_defs]
|
|
206
|
+
|
|
207
|
+
_add_custom_fields_to_control(response['control'], custom_field_controls)
|
|
208
|
+
|
|
209
|
+
@staticmethod
|
|
210
|
+
def _extract_class_name(request: Any) -> str | None:
|
|
211
|
+
"""Extract class name from request.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
request: The request object
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
The class name or None if not found
|
|
218
|
+
"""
|
|
219
|
+
if hasattr(request, 'query_params') and 'class_name' in request.query_params:
|
|
220
|
+
return request.query_params['class_name']
|
|
221
|
+
|
|
222
|
+
if hasattr(request, 'path_params') and 'address' in request.path_params:
|
|
223
|
+
return Address.from_string(request.path_params['address']).class_name
|
|
224
|
+
|
|
225
|
+
return None
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
amsdal_crm/
|
|
1
|
+
amsdal_crm/Third-Party Materials - AMSDAL Dependencies - License Notices.md,sha256=ML7PqsHrTMNNZn8E_rA-LzDCAafMSxMcrmSg8YOi-wo,113896
|
|
2
|
+
amsdal_crm/__about__.py,sha256=ls1camlIoMxEZz9gSkZ1OJo-MXqHWwKPtdPbZJmwp7E,22
|
|
2
3
|
amsdal_crm/__init__.py,sha256=b4wxJYesA5Ctk1IrAvlw64i_0EU3SiK1Tw6sUYakd18,303
|
|
3
|
-
amsdal_crm/app.py,sha256=
|
|
4
|
+
amsdal_crm/app.py,sha256=JLvueh_2KURQLDWMQlq4Z6gAFsqBDTRf6pK095ehp14,1373
|
|
4
5
|
amsdal_crm/constants.py,sha256=5Ga7q9zEKcQZnAoKv_SE_7w8WxvhPFkM9gY9NruOEaA,347
|
|
5
6
|
amsdal_crm/errors.py,sha256=kTcDyKb-sEWkoYQ6OtokyzLIySMdOq32O3_qoFYUXuQ,514
|
|
6
7
|
amsdal_crm/settings.py,sha256=YbwDeiKaahqipGoBGkMRzYKGk8flt7IrkmMTLDyC9OQ,751
|
|
@@ -8,7 +9,7 @@ amsdal_crm/fixtures/__init__.py,sha256=1tDNXZhcbZBd4tX3lTKKlom1NUg1TX2aa2IbymWO9
|
|
|
8
9
|
amsdal_crm/fixtures/permissions.py,sha256=cYA-gWkKQdoN79GymQVHtT0GyFXMzaskwp13Ietp9wE,1107
|
|
9
10
|
amsdal_crm/fixtures/pipelines.py,sha256=ZCLmgrA700Sl7Oy7l4IQ8FbIbC1378OkcJTrZe5701o,2064
|
|
10
11
|
amsdal_crm/lifecycle/__init__.py,sha256=B8nw19lEIr7U15Lnu6jh7yzZwF9LWWh4-p3X63sAicQ,31
|
|
11
|
-
amsdal_crm/lifecycle/consumer.py,sha256=
|
|
12
|
+
amsdal_crm/lifecycle/consumer.py,sha256=i8QjD16DuYG5JYNPMBoxfkWJX8leVZfEjEL_3V6GHbI,7253
|
|
12
13
|
amsdal_crm/migrations/0000_initial.py,sha256=8XjM-sbrNKJfcyGE_K2ITW4fOz7gmUxWH_NbX48o4XI,57028
|
|
13
14
|
amsdal_crm/models/__init__.py,sha256=DSuGeLKPNL_EUGohWtrH6Eof6Nk--dHyZpfqbGWmYIY,1350
|
|
14
15
|
amsdal_crm/models/account.py,sha256=b9JguizB-eM1BkDar4nGhayZ-icdfIsQp5cdB4sVZaQ,4897
|
|
@@ -26,6 +27,6 @@ amsdal_crm/services/custom_field_service.py,sha256=rqTlzcmjc-tqc3nuo2m0cMFdMgo1l
|
|
|
26
27
|
amsdal_crm/services/deal_service.py,sha256=ZF7cAc6r10xgXZ8D8Oy3hnE-6GgAXq8fuG_Y4QerRGw,4839
|
|
27
28
|
amsdal_crm/services/email_service.py,sha256=kung83otZAzm5MjezbWFP_Bp9CJ2NXLlDdjMX9MvNIc,3788
|
|
28
29
|
amsdal_crm/services/workflow_service.py,sha256=oKOFJrwnMZxAiHuScs63tZxL801z-6ryrUuPMC7OXlE,7036
|
|
29
|
-
amsdal_crm-0.1.
|
|
30
|
-
amsdal_crm-0.1.
|
|
31
|
-
amsdal_crm-0.1.
|
|
30
|
+
amsdal_crm-0.1.1.dist-info/METADATA,sha256=3Aj2I9q7wS0OW7CVgMLWdpFHDPkM_qpukZdr9OoQL7A,1596
|
|
31
|
+
amsdal_crm-0.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
32
|
+
amsdal_crm-0.1.1.dist-info/RECORD,,
|
|
File without changes
|