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/__about__.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.1.0'
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)
@@ -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,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amsdal_crm
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: amsdal-crm plugin for AMSDAL Framework
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: aiohttp==3.12.15
@@ -1,6 +1,7 @@
1
- amsdal_crm/__about__.py,sha256=IMjkMO3twhQzluVTo8Z6rE7Eg-9U79_LGKMcsWLKBkY,22
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=eQV30L4QB43jjmGQzK_2crLmKK9P886KdFz7zTyDB8Y,1029
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=7tjPxWYMUZY0x77IrzGYPbaq2ozqDpflkyn17G5E2Zc,1293
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.0.dist-info/METADATA,sha256=xZ3eDOo_eJ0pTI8uqQYCkS-F1Cgax3lx86xlZBs-cak,1596
30
- amsdal_crm-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
31
- amsdal_crm-0.1.0.dist-info/RECORD,,
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,,