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.
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/PKG-INFO +1 -1
- amsdal_crm-0.1.1/amsdal_crm/__about__.py +1 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/app.py +5 -0
- amsdal_crm-0.1.1/amsdal_crm/lifecycle/consumer.py +225 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/license_check.py +1 -1
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/uv.lock +13 -13
- amsdal_crm-0.1.0/amsdal_crm/__about__.py +0 -1
- amsdal_crm-0.1.0/amsdal_crm/lifecycle/consumer.py +0 -44
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/.amsdal/.environment +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/.amsdal-cli +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/.github/workflows/ci.yml +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/.github/workflows/release.yml +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/.github/workflows/tag_check.yml +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/.gitignore +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/CLAUDE.md +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/README.md +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/RELEASE.md +0 -0
- {amsdal_crm-0.1.0/amsdal_ml → amsdal_crm-0.1.1/amsdal_crm}/Third-Party Materials - AMSDAL Dependencies - License Notices.md +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/__init__.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/constants.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/errors.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/fixtures/__init__.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/fixtures/permissions.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/fixtures/pipelines.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/lifecycle/__init__.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/migrations/0000_initial.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/models/__init__.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/models/account.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/models/activity.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/models/attachment.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/models/contact.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/models/custom_field_definition.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/models/deal.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/models/pipeline.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/models/stage.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/models/workflow_rule.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/services/__init__.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/services/activity_service.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/services/custom_field_service.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/services/deal_service.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/services/email_service.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/services/workflow_service.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/amsdal_crm/settings.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/config.yml +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/pyproject.toml +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/scripts/release.sh +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/scripts/tag_check.sh +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/conftest.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/integration/__init__.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/integration/conftest.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/integration/services/__init__.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/integration/services/test_deal_service.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/integration/services/test_email_service.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/integration/services/test_workflow_service.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/__init__.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/conftest.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/lifecycle/__init__.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/lifecycle/test_consumer.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/models/__init__.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/models/test_account.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/models/test_activity.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/models/test_contact.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/models/test_deal.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/models/test_pipeline.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/models/test_remaining.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/models/test_stage.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/services/__init__.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/services/test_activity_service.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/services/test_custom_field_service.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/services/test_deal_service.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/services/test_email_service.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/services/test_workflow_service.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/test_app.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/test_constants.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/test_errors.py +0 -0
- {amsdal_crm-0.1.0 → amsdal_crm-0.1.1}/tests/unit/test_settings.py +0 -0
|
@@ -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 = './
|
|
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.
|
|
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/
|
|
323
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
324
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
325
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
326
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
327
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
328
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
329
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
330
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
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.
|
|
3204
|
+
version = "2.8.3"
|
|
3205
3205
|
source = { registry = "https://pypi.org/simple" }
|
|
3206
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
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/
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|