amsdal_crm 0.1.0__py3-none-any.whl → 0.1.2__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.2'
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,181 @@ 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'{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
+ if 'control' in control:
102
+ control.pop('control', None)
103
+
104
+ # Find the custom_fields control and update it, or append custom fields at the end
105
+ custom_fields_control = None
106
+ for ctrl in control.get('controls', []):
107
+ if ctrl.get('name') == 'custom_fields':
108
+ custom_fields_control = ctrl
109
+ break
110
+
111
+ if custom_fields_control:
112
+ # Update the custom_fields control to be a group with nested controls
113
+ custom_fields_control['type'] = 'group'
114
+ custom_fields_control['controls'] = custom_field_controls
115
+ else:
116
+ # Add custom fields as a new group at the end
117
+ control['controls'].append(
118
+ {
119
+ 'type': 'group',
120
+ 'name': 'custom_fields',
121
+ 'label': 'Custom Fields',
122
+ 'controls': custom_field_controls,
123
+ }
124
+ )
125
+
126
+
127
+ class CustomAttributesFrontendConfigConsumer(LifecycleConsumer):
128
+ """Consumer that propagates custom attributes into frontend config.
129
+
130
+ This consumer listens to response events and adds custom field definitions
131
+ as frontend controls for CRM entities (Contact, Account, Deal).
132
+ """
133
+
134
+ def on_event(
135
+ self,
136
+ request: Any,
137
+ response: dict[str, Any],
138
+ ) -> None:
139
+ """Handle response event by adding custom field controls.
140
+
141
+ Args:
142
+ request: The request object containing query and path parameters.
143
+ response: The response dictionary to be processed.
144
+ """
145
+ from amsdal_crm.models.custom_field_definition import CustomFieldDefinition
146
+
147
+ class_name = self._extract_class_name(request)
148
+
149
+ if class_name not in CRM_ENTITY_TYPES:
150
+ return
151
+
152
+ if not isinstance(response, dict) or 'control' not in response:
153
+ return
154
+
155
+ custom_field_defs = list(
156
+ CustomFieldDefinition.objects.filter(
157
+ entity_type=class_name,
158
+ _metadata__is_deleted=False,
159
+ _address__object_version=Versions.LATEST,
160
+ ).execute()
161
+ )
162
+
163
+ if not custom_field_defs:
164
+ return
165
+
166
+ # Sort by display_order
167
+ custom_field_defs.sort(key=lambda x: x.display_order)
168
+
169
+ custom_field_controls = [_custom_field_def_to_control(field_def) for field_def in custom_field_defs]
170
+
171
+ _add_custom_fields_to_control(response['control'], custom_field_controls)
172
+
173
+ async def on_event_async(
174
+ self,
175
+ request: Any,
176
+ response: dict[str, Any],
177
+ ) -> None:
178
+ """Async version of on_event.
179
+
180
+ Args:
181
+ request: The request object containing query and path parameters.
182
+ response: The response dictionary to be processed.
183
+ """
184
+ from amsdal_crm.models.custom_field_definition import CustomFieldDefinition
185
+
186
+ class_name = self._extract_class_name(request)
187
+
188
+ if class_name not in CRM_ENTITY_TYPES:
189
+ return
190
+
191
+ if not isinstance(response, dict) or 'control' not in response:
192
+ return
193
+
194
+ custom_field_defs = list(
195
+ await CustomFieldDefinition.objects.filter(
196
+ entity_type=class_name,
197
+ _metadata__is_deleted=False,
198
+ _address__object_version=Versions.LATEST,
199
+ ).aexecute()
200
+ )
201
+
202
+ if not custom_field_defs:
203
+ return
204
+
205
+ # Sort by display_order
206
+ custom_field_defs.sort(key=lambda x: x.display_order)
207
+
208
+ custom_field_controls = [_custom_field_def_to_control(field_def) for field_def in custom_field_defs]
209
+
210
+ _add_custom_fields_to_control(response['control'], custom_field_controls)
211
+
212
+ @staticmethod
213
+ def _extract_class_name(request: Any) -> str | None:
214
+ """Extract class name from request.
215
+
216
+ Args:
217
+ request: The request object
218
+
219
+ Returns:
220
+ The class name or None if not found
221
+ """
222
+ if hasattr(request, 'query_params') and 'class_name' in request.query_params:
223
+ return request.query_params['class_name']
224
+
225
+ if hasattr(request, 'path_params') and 'address' in request.path_params:
226
+ return Address.from_string(request.path_params['address']).class_name
227
+
228
+ 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.2
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=mdp2CftfqYbdKtP-eWv1z7rAUycYv6X1ntXSMUf8Kss,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=2xNiFb4Xz0Xsa_1wL5HosKkb4kDMuYOhCd643qEYqCA,7306
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.2.dist-info/METADATA,sha256=bkg5fEm_Mr2pPj0ScM1kS0XySZ5Akc6PIO_K2JNnOog,1596
31
+ amsdal_crm-0.1.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
32
+ amsdal_crm-0.1.2.dist-info/RECORD,,