amsdal_crm 0.1.0__tar.gz → 0.1.1__tar.gz

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.
Files changed (76) hide show
  1. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/PKG-INFO +1 -1
  2. amsdal_crm-0.1.1/amsdal_crm/__about__.py +1 -0
  3. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/app.py +5 -0
  4. amsdal_crm-0.1.1/amsdal_crm/lifecycle/consumer.py +225 -0
  5. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/license_check.py +1 -1
  6. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/uv.lock +13 -13
  7. amsdal_crm-0.1.0/amsdal_crm/__about__.py +0 -1
  8. amsdal_crm-0.1.0/amsdal_crm/lifecycle/consumer.py +0 -44
  9. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/.amsdal/.environment +0 -0
  10. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/.amsdal-cli +0 -0
  11. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/.github/workflows/ci.yml +0 -0
  12. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/.github/workflows/release.yml +0 -0
  13. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/.github/workflows/tag_check.yml +0 -0
  14. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/.gitignore +0 -0
  15. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/CLAUDE.md +0 -0
  16. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/README.md +0 -0
  17. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/RELEASE.md +0 -0
  18. {amsdal_crm-0.1.0/amsdal_ml → amsdal_crm-0.1.1/amsdal_crm}/Third-Party Materials - AMSDAL Dependencies - License Notices.md +0 -0
  19. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/__init__.py +0 -0
  20. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/constants.py +0 -0
  21. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/errors.py +0 -0
  22. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/fixtures/__init__.py +0 -0
  23. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/fixtures/permissions.py +0 -0
  24. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/fixtures/pipelines.py +0 -0
  25. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/lifecycle/__init__.py +0 -0
  26. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/migrations/0000_initial.py +0 -0
  27. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/models/__init__.py +0 -0
  28. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/models/account.py +0 -0
  29. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/models/activity.py +0 -0
  30. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/models/attachment.py +0 -0
  31. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/models/contact.py +0 -0
  32. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/models/custom_field_definition.py +0 -0
  33. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/models/deal.py +0 -0
  34. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/models/pipeline.py +0 -0
  35. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/models/stage.py +0 -0
  36. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/models/workflow_rule.py +0 -0
  37. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/services/__init__.py +0 -0
  38. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/services/activity_service.py +0 -0
  39. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/services/custom_field_service.py +0 -0
  40. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/services/deal_service.py +0 -0
  41. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/services/email_service.py +0 -0
  42. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/services/workflow_service.py +0 -0
  43. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/settings.py +0 -0
  44. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/config.yml +0 -0
  45. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/pyproject.toml +0 -0
  46. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/scripts/release.sh +0 -0
  47. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/scripts/tag_check.sh +0 -0
  48. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/conftest.py +0 -0
  49. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/integration/__init__.py +0 -0
  50. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/integration/conftest.py +0 -0
  51. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/integration/services/__init__.py +0 -0
  52. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/integration/services/test_deal_service.py +0 -0
  53. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/integration/services/test_email_service.py +0 -0
  54. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/integration/services/test_workflow_service.py +0 -0
  55. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/__init__.py +0 -0
  56. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/conftest.py +0 -0
  57. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/lifecycle/__init__.py +0 -0
  58. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/lifecycle/test_consumer.py +0 -0
  59. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/models/__init__.py +0 -0
  60. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/models/test_account.py +0 -0
  61. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/models/test_activity.py +0 -0
  62. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/models/test_contact.py +0 -0
  63. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/models/test_deal.py +0 -0
  64. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/models/test_pipeline.py +0 -0
  65. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/models/test_remaining.py +0 -0
  66. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/models/test_stage.py +0 -0
  67. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/services/__init__.py +0 -0
  68. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/services/test_activity_service.py +0 -0
  69. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/services/test_custom_field_service.py +0 -0
  70. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/services/test_deal_service.py +0 -0
  71. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/services/test_email_service.py +0 -0
  72. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/services/test_workflow_service.py +0 -0
  73. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/test_app.py +0 -0
  74. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/test_constants.py +0 -0
  75. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/test_errors.py +0 -0
  76. {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/test_settings.py +0 -0
@@ -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
@@ -0,0 +1 @@
1
+ __version__ = '0.1.1'
@@ -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)
@@ -0,0 +1,225 @@
1
+ """CRM Lifecycle Consumers."""
2
+
3
+ from typing import TYPE_CHECKING
4
+ from typing import Any
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
9
+
10
+ if TYPE_CHECKING:
11
+ from amsdal_crm.models.deal import Deal
12
+
13
+ # Entity types that support custom fields
14
+ CRM_ENTITY_TYPES = {'Contact', 'Account', 'Deal'}
15
+
16
+
17
+ class LoadCRMFixturesConsumer(LifecycleConsumer):
18
+ """Consumer that loads CRM fixtures on server startup."""
19
+
20
+ def on_event(self) -> None:
21
+ """Load CRM fixtures (pipelines, stages, permissions)."""
22
+ # Note: Fixtures are typically loaded via the FixturesManager
23
+ # This consumer can be used for additional setup if needed
24
+ pass
25
+
26
+ async def on_event_async(self) -> None:
27
+ """Async version of on_event."""
28
+ pass
29
+
30
+
31
+ class DealWonNotificationConsumer(LifecycleConsumer):
32
+ """Consumer that handles deal won events.
33
+
34
+ Placeholder for future notification system integration.
35
+ """
36
+
37
+ def on_event(self, deal: 'Deal', user_email: str) -> None:
38
+ """Handle deal won event.
39
+
40
+ Args:
41
+ deal: The deal that was won
42
+ user_email: Email of user who closed the deal
43
+ """
44
+ # TODO: Implement notification logic
45
+ # Could integrate with email service, Slack, etc.
46
+ pass
47
+
48
+ async def on_event_async(self, deal: 'Deal', user_email: str) -> None:
49
+ """Async version of on_event."""
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
@@ -20,7 +20,7 @@ def extract_dependency_names():
20
20
  def check_dependencies():
21
21
  flag = True
22
22
  dependency_list = extract_dependency_names()
23
- file_path = './amsdal_ml/Third-Party Materials - AMSDAL Dependencies - License Notices.md'
23
+ file_path = './amsdal_crm/Third-Party Materials - AMSDAL Dependencies - License Notices.md'
24
24
  with open(file_path, encoding='utf-8') as file:
25
25
  content = file.read()
26
26
 
@@ -305,7 +305,7 @@ wheels = [
305
305
 
306
306
  [[package]]
307
307
  name = "amsdal-models"
308
- version = "0.5.31"
308
+ version = "0.5.32"
309
309
  source = { registry = "https://pypi.org/simple" }
310
310
  dependencies = [
311
311
  { name = "amsdal-data" },
@@ -319,15 +319,15 @@ dependencies = [
319
319
  { name = "ruff" },
320
320
  ]
321
321
  wheels = [
322
- { url = "https://files.pythonhosted.org/packages/09/4e/7f174563615905891ef44e9e52038d58c2ab7c501fd93c42cb17ea04dede/amsdal_models-0.5.31-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3a8ffb63e042ad6ea2301c719a34a882c2b6a45485905f1e0cc8a09b37b30dc1", size = 817664, upload-time = "2026-01-16T16:04:33.323Z" },
323
- { url = "https://files.pythonhosted.org/packages/7d/09/bd734e25162f4775b6615defbd244e089f1cef6b109841cb7bd4d0a28cf0/amsdal_models-0.5.31-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5e08289c211802baa9993af54a63564b3e67fb29ec11434057e9a261e7dd867", size = 2085093, upload-time = "2026-01-16T16:04:34.698Z" },
324
- { url = "https://files.pythonhosted.org/packages/1a/95/2f269aac72ed3b9dcdab1e6e38efce399382f6d3903130f2c88888aff08f/amsdal_models-0.5.31-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85428c4d81ce1aceea9777264832e64716334ae8aa8db1550e38340bcf2ca61d", size = 2020380, upload-time = "2026-01-16T16:04:27.64Z" },
325
- { url = "https://files.pythonhosted.org/packages/17/a9/505eb21c265651a80911e260827631f32071686fcdc73165a878c4c0bd97/amsdal_models-0.5.31-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8c5df8183078d403daac139c73972c3f3c7983c906bf12fc6b27c9f02ec5717e", size = 803616, upload-time = "2026-01-16T16:04:36.25Z" },
326
- { url = "https://files.pythonhosted.org/packages/cf/86/5e90cb21749d1390383eaa4a812e5d60859ef1f6b5e416bea62dd2ae9523/amsdal_models-0.5.31-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ffe27d16833e6905da3fc0b10919a5b498a9a73498e7347768c3f59b8df2930", size = 2137176, upload-time = "2026-01-16T16:04:24.286Z" },
327
- { url = "https://files.pythonhosted.org/packages/3b/e5/edcdacb2f7b57f7db7c1b59c8a2351a711a6b8dbbdd1982395ef5679bc0c/amsdal_models-0.5.31-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b425f1e1dd23678d4a0ca73e0ee79bc0daa9ffaacd2639c0fd4a7e67a8b1db60", size = 2080202, upload-time = "2026-01-16T16:04:25.881Z" },
328
- { url = "https://files.pythonhosted.org/packages/95/ef/1c79ed09bf26205423854629c4f97ea45c815ed209aad0be223640793194/amsdal_models-0.5.31-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:800799121d53daf6edcabce546de583c261d02b1889efc2c807557970e8ba121", size = 794862, upload-time = "2026-01-16T16:04:31.585Z" },
329
- { url = "https://files.pythonhosted.org/packages/77/4e/16761d4a1538b2ecd5e892f12a6b0422befa68e4aa084ed6293950102387/amsdal_models-0.5.31-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ca9db89a47862ce1c863bdc06bb757e5936e07f62dd1ae97eef5041b8206eb1", size = 2088169, upload-time = "2026-01-16T16:04:29.939Z" },
330
- { url = "https://files.pythonhosted.org/packages/cd/bf/e525555322a266a62f11db00ed519f7dad5c1682a7afc565932221eac829/amsdal_models-0.5.31-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6e3239a5dc36b43b3cf2449069afc3997bce8bc4eb4ae34d1526c6c291560b0c", size = 2044112, upload-time = "2026-01-16T16:04:37.556Z" },
322
+ { url = "https://files.pythonhosted.org/packages/f2/ea/876ddec8b6e54ea5cd2e3ad1934d145dca17605d4389824e7e0fafd410e0/amsdal_models-0.5.32-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:729c5d73f9b4255ca43b524e5a8d91cff00a8287b10e7da199309a120bd558eb", size = 817883, upload-time = "2026-01-20T11:58:07.784Z" },
323
+ { url = "https://files.pythonhosted.org/packages/db/9d/fc954512cef82c2ecf8d029ad3a5f3caaf97866a790f074981119e2735b3/amsdal_models-0.5.32-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7a320c3b7dda65ee63ca266de5b652b51aedef5b2c1396c3611bc50b6ede35b", size = 2085312, upload-time = "2026-01-20T11:58:00.808Z" },
324
+ { url = "https://files.pythonhosted.org/packages/46/13/4a86db48c9469eb0cdc3743b8f3967b045b8e92b5cadb5e22f429ae8a158/amsdal_models-0.5.32-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b6ef552e96dbcbd4ded8563be68da9bf9cef892b1095856636b70ab837efb31f", size = 2020596, upload-time = "2026-01-20T11:58:02.422Z" },
325
+ { url = "https://files.pythonhosted.org/packages/30/78/904b182d137e9eb851727fefeea2a8c3da08a117e7f1383480049fc01955/amsdal_models-0.5.32-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4fb4e26531c0a8983ebeac55cdca6f6f425ffb95d0736f6223661ab42a48b63d", size = 803824, upload-time = "2026-01-20T11:58:06.251Z" },
326
+ { url = "https://files.pythonhosted.org/packages/64/89/a1d9f7f1b6c6babb468496f2f9e7415bc26cd02d2418c75354226fefc903/amsdal_models-0.5.32-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7145e6717d68d764cb9fb26f9f29bb3b29ca0c1c6b82ae849891eae32291d513", size = 2137391, upload-time = "2026-01-20T11:58:03.71Z" },
327
+ { url = "https://files.pythonhosted.org/packages/80/a8/d2662431bc46a0a3b788b9074a839192bd24fd02f7d8a5e1960a20e00bf5/amsdal_models-0.5.32-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:003871431344c80dfb879eec399f60ed8acfc49ef19fa29c5573f924d85e36c1", size = 2080421, upload-time = "2026-01-20T11:58:13.072Z" },
328
+ { url = "https://files.pythonhosted.org/packages/31/f1/f2a903b5843e3c84a866fbc01265e1aa7c703c41ecc8a47f9350c05e9c2c/amsdal_models-0.5.32-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9081b00e74f5101ec58532e1b8c55b677762c9253e06e688ac1f5d2f6013f4bb", size = 795073, upload-time = "2026-01-20T11:58:09.057Z" },
329
+ { url = "https://files.pythonhosted.org/packages/74/ba/d7ca35ae7f5cb199c1515e449c8b0918437d9b20fa759f679c76934099fb/amsdal_models-0.5.32-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bc744bb592583de8022da6827b7f0b2f42100aed5f64d39e281ba0ac30bd4bc", size = 2088388, upload-time = "2026-01-20T11:58:11.407Z" },
330
+ { url = "https://files.pythonhosted.org/packages/d1/85/66268b8fcbfda2436f821b817658b8b63e6395e735a5bf6035bfb26b0970/amsdal_models-0.5.32-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8da46198c2510a0a1837f309523e5007621b45d840bf6a9fe4155041868b40c9", size = 2044327, upload-time = "2026-01-20T11:58:05.067Z" },
331
331
  ]
332
332
 
333
333
  [[package]]
@@ -3201,11 +3201,11 @@ wheels = [
3201
3201
 
3202
3202
  [[package]]
3203
3203
  name = "soupsieve"
3204
- version = "2.8.2"
3204
+ version = "2.8.3"
3205
3205
  source = { registry = "https://pypi.org/simple" }
3206
- sdist = { url = "https://files.pythonhosted.org/packages/93/f2/21d6ca70c3cf35d01ae9e01be534bf6b6b103c157a728082a5028350c310/soupsieve-2.8.2.tar.gz", hash = "sha256:78a66b0fdee2ab40b7199dc3e747ee6c6e231899feeaae0b9b98a353afd48fd8", size = 118601, upload-time = "2026-01-18T16:21:31.09Z" }
3206
+ sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" }
3207
3207
  wheels = [
3208
- { url = "https://files.pythonhosted.org/packages/a6/9a/b4450ccce353e2430621b3bb571899ffe1033d5cd72c9e065110f95b1a63/soupsieve-2.8.2-py3-none-any.whl", hash = "sha256:0f4c2f6b5a5fb97a641cf69c0bd163670a0e45e6d6c01a2107f93a6a6f93c51a", size = 37016, upload-time = "2026-01-18T16:21:29.7Z" },
3208
+ { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" },
3209
3209
  ]
3210
3210
 
3211
3211
  [[package]]
@@ -1 +0,0 @@
1
- __version__ = '0.1.0'
@@ -1,44 +0,0 @@
1
- """CRM Lifecycle Consumers."""
2
-
3
- from typing import TYPE_CHECKING
4
-
5
- from amsdal_utils.lifecycle.consumer import LifecycleConsumer
6
-
7
- if TYPE_CHECKING:
8
- from amsdal_crm.models.deal import Deal
9
-
10
-
11
- class LoadCRMFixturesConsumer(LifecycleConsumer):
12
- """Consumer that loads CRM fixtures on server startup."""
13
-
14
- def on_event(self) -> None:
15
- """Load CRM fixtures (pipelines, stages, permissions)."""
16
- # Note: Fixtures are typically loaded via the FixturesManager
17
- # This consumer can be used for additional setup if needed
18
- pass
19
-
20
- async def on_event_async(self) -> None:
21
- """Async version of on_event."""
22
- pass
23
-
24
-
25
- class DealWonNotificationConsumer(LifecycleConsumer):
26
- """Consumer that handles deal won events.
27
-
28
- Placeholder for future notification system integration.
29
- """
30
-
31
- def on_event(self, deal: 'Deal', user_email: str) -> None:
32
- """Handle deal won event.
33
-
34
- Args:
35
- deal: The deal that was won
36
- user_email: Email of user who closed the deal
37
- """
38
- # TODO: Implement notification logic
39
- # Could integrate with email service, Slack, etc.
40
- pass
41
-
42
- async def on_event_async(self, deal: 'Deal', user_email: str) -> None:
43
- """Async version of on_event."""
44
- pass
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes