amsdal_crm 0.2.1__tar.gz → 0.2.3__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.2.1 → amsdal_crm-0.2.3}/PKG-INFO +1 -1
- amsdal_crm-0.2.3/amsdal_crm/__about__.py +1 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/migrations/0000_initial.py +12 -8
- amsdal_crm-0.2.3/amsdal_crm/models/common.py +45 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/models/deal.py +4 -35
- amsdal_crm-0.2.3/amsdal_crm/models/entity.py +122 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/pyproject.toml +5 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/uv.lock +362 -363
- amsdal_crm-0.2.1/amsdal_crm/__about__.py +0 -1
- amsdal_crm-0.2.1/amsdal_crm/models/entity.py +0 -326
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/.amsdal/.environment +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/.amsdal-cli +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/.github/workflows/ci.yml +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/.github/workflows/release.yml +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/.github/workflows/tag_check.yml +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/.gitignore +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/CLAUDE.md +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/README.md +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/RELEASE.md +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/Third-Party Materials - AMSDAL Dependencies - License Notices.md +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/__init__.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/app.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/constants.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/errors.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/fixtures/__init__.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/fixtures/permissions.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/fixtures/pipelines.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/lifecycle/__init__.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/lifecycle/consumer.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/models/__init__.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/models/activity.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/models/attachment.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/models/custom_field_definition.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/models/pipeline.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/models/stage.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/models/workflow_rule.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/services/__init__.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/services/activity_service.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/services/custom_field_service.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/services/deal_service.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/services/email_service.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/services/workflow_service.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/amsdal_crm/settings.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/config.yml +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/license_check.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/scripts/release.sh +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/scripts/tag_check.sh +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/conftest.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/integration/__init__.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/integration/conftest.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/integration/services/__init__.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/integration/services/test_deal_service.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/integration/services/test_email_service.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/integration/services/test_workflow_service.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/__init__.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/conftest.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/lifecycle/__init__.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/lifecycle/test_consumer.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/models/__init__.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/models/test_activity.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/models/test_deal.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/models/test_entity.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/models/test_pipeline.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/models/test_remaining.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/models/test_stage.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/services/__init__.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/services/test_activity_service.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/services/test_custom_field_service.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/services/test_deal_service.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/services/test_email_service.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/services/test_workflow_service.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/test_app.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/test_constants.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/test_errors.py +0 -0
- {amsdal_crm-0.2.1 → amsdal_crm-0.2.3}/tests/unit/test_settings.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.2.3'
|
|
@@ -65,6 +65,7 @@ class Migration(migrations.Migration):
|
|
|
65
65
|
"properties": {
|
|
66
66
|
"created_at": {"type": "datetime", "title": "Created At", "format": "date-time"},
|
|
67
67
|
"updated_at": {"type": "datetime", "title": "Updated At", "format": "date-time"},
|
|
68
|
+
"custom_fields": {"type": "anything", "title": "Custom Fields"},
|
|
68
69
|
"name": {"type": "string", "title": "Entity Name"},
|
|
69
70
|
"legal_name": {"type": "string", "title": "Legal Name"},
|
|
70
71
|
"status": {
|
|
@@ -75,10 +76,9 @@ class Migration(migrations.Migration):
|
|
|
75
76
|
"enum": ["Active", "Inactive"],
|
|
76
77
|
},
|
|
77
78
|
"note": {"type": "string", "title": "Note"},
|
|
78
|
-
"custom_fields": {"type": "anything", "title": "Custom Fields"},
|
|
79
79
|
"assigned_to": {"type": "User", "title": "Assigned To"},
|
|
80
80
|
},
|
|
81
|
-
"custom_code": 'from amsdal.contrib.auth.models.user import User\n\n\n@property\ndef display_name(self) -> str:\n """Return display name for the account."""\n return self.name\n\nasync def
|
|
81
|
+
"custom_code": 'from amsdal.contrib.auth.models.user import User\n\n\n@classmethod\ndef custom_fields_cell_template(cls) -> str:\n return \'JsonTemplate\'\n\n@property\ndef display_name(self) -> str:\n """Return display name for the account."""\n return self.name\n\nasync def _avalidate_custom_fields(self) -> None:\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = await CustomFieldService.avalidate_custom_fields(self.__class__.__name__, self.custom_fields)\n\nasync def apost_update(self) -> None:\n """Async hook called after updating account."""\n from amsdal_crm.services.workflow_service import WorkflowService\n await WorkflowService.aexecute_rules(\'Entity\', \'update\', self)\n\nasync def apre_create(self) -> None:\n await self._avalidate_custom_fields()\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n await self._avalidate_custom_fields()\n await super().apre_update()\n\ndef _validate_custom_fields(self) -> None:\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = CustomFieldService.validate_custom_fields(self.__class__.__name__, self.custom_fields)\n\ndef has_object_permission(self, user: \'User\', action: str) -> bool:\n """Check if user has permission to perform action on this account.\n\n Args:\n user: The user attempting the action\n action: The action being attempted (read, create, update, delete)\n\n Returns:\n True if user has permission, False otherwise\n """\n if self.assigned_to and self.assigned_to.email == user.email:\n return True\n if user.permissions:\n for permission in user.permissions:\n if permission.model == \'*\' and permission.action in (\'*\', action):\n return True\n if permission.model == \'Entity\' and permission.action in (\'*\', action):\n return True\n return False\n\ndef post_update(self) -> None:\n """Hook called after updating account."""\n from amsdal_crm.services.workflow_service import WorkflowService\n WorkflowService.execute_rules(\'Entity\', \'update\', self)\n\ndef pre_create(self) -> None:\n self._validate_custom_fields()\n super().pre_create()\n\ndef pre_update(self) -> None:\n self._validate_custom_fields()\n super().pre_update()',
|
|
82
82
|
"storage_metadata": {
|
|
83
83
|
"table_name": "Entity",
|
|
84
84
|
"db_fields": {"assigned_to": ["assigned_to_partition_key"]},
|
|
@@ -100,6 +100,7 @@ class Migration(migrations.Migration):
|
|
|
100
100
|
"properties": {
|
|
101
101
|
"created_at": {"type": "datetime", "title": "Created At", "format": "date-time"},
|
|
102
102
|
"updated_at": {"type": "datetime", "title": "Updated At", "format": "date-time"},
|
|
103
|
+
"custom_fields": {"type": "anything", "title": "Custom Fields"},
|
|
103
104
|
"line1": {"type": "string", "title": "Address Line 1"},
|
|
104
105
|
"line2": {"type": "string", "title": "Address Line 2"},
|
|
105
106
|
"city": {"type": "string", "title": "City"},
|
|
@@ -108,7 +109,7 @@ class Migration(migrations.Migration):
|
|
|
108
109
|
"country": {"type": "string", "title": "Country"},
|
|
109
110
|
"is_primary": {"type": "boolean", "default": False, "title": "Is Primary"},
|
|
110
111
|
},
|
|
111
|
-
"custom_code":
|
|
112
|
+
"custom_code": "@classmethod\ndef custom_fields_cell_template(cls) -> str:\n return 'JsonTemplate'\n\nasync def _avalidate_custom_fields(self) -> None:\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = await CustomFieldService.avalidate_custom_fields(self.__class__.__name__, self.custom_fields)\n\nasync def apre_create(self) -> None:\n await self._avalidate_custom_fields()\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n await self._avalidate_custom_fields()\n await super().apre_update()\n\ndef _validate_custom_fields(self) -> None:\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = CustomFieldService.validate_custom_fields(self.__class__.__name__, self.custom_fields)\n\ndef pre_create(self) -> None:\n self._validate_custom_fields()\n super().pre_create()\n\ndef pre_update(self) -> None:\n self._validate_custom_fields()\n super().pre_update()",
|
|
112
113
|
"storage_metadata": {
|
|
113
114
|
"table_name": "EntityAddress",
|
|
114
115
|
"db_fields": {},
|
|
@@ -372,6 +373,7 @@ class Migration(migrations.Migration):
|
|
|
372
373
|
"properties": {
|
|
373
374
|
"created_at": {"type": "datetime", "title": "Created At", "format": "date-time"},
|
|
374
375
|
"updated_at": {"type": "datetime", "title": "Updated At", "format": "date-time"},
|
|
376
|
+
"custom_fields": {"type": "anything", "title": "Custom Fields"},
|
|
375
377
|
"value": {"type": "string", "title": "Contact Point Value"},
|
|
376
378
|
"is_primary": {"type": "boolean", "default": False, "title": "Is Primary"},
|
|
377
379
|
"can_contact": {"type": "boolean", "default": True, "title": "Can Contact"},
|
|
@@ -381,7 +383,7 @@ class Migration(migrations.Migration):
|
|
|
381
383
|
"description": "Entity (Person/Organization/Trust) model.\n\nRepresents a company or organization in the CRM system.\nOwned by individual users with permission controls.",
|
|
382
384
|
},
|
|
383
385
|
},
|
|
384
|
-
"custom_code":
|
|
386
|
+
"custom_code": "@classmethod\ndef custom_fields_cell_template(cls) -> str:\n return 'JsonTemplate'\n\nasync def _avalidate_custom_fields(self) -> None:\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = await CustomFieldService.avalidate_custom_fields(self.__class__.__name__, self.custom_fields)\n\nasync def apre_create(self) -> None:\n await self._avalidate_custom_fields()\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n await self._avalidate_custom_fields()\n await super().apre_update()\n\ndef _validate_custom_fields(self) -> None:\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = CustomFieldService.validate_custom_fields(self.__class__.__name__, self.custom_fields)\n\ndef pre_create(self) -> None:\n self._validate_custom_fields()\n super().pre_create()\n\ndef pre_update(self) -> None:\n self._validate_custom_fields()\n super().pre_update()",
|
|
385
387
|
"storage_metadata": {
|
|
386
388
|
"table_name": "EntityContactPoint",
|
|
387
389
|
"db_fields": {"entity": ["entity_partition_key"]},
|
|
@@ -399,6 +401,7 @@ class Migration(migrations.Migration):
|
|
|
399
401
|
"properties": {
|
|
400
402
|
"created_at": {"type": "datetime", "title": "Created At", "format": "date-time"},
|
|
401
403
|
"updated_at": {"type": "datetime", "title": "Updated At", "format": "date-time"},
|
|
404
|
+
"custom_fields": {"type": "anything", "title": "Custom Fields"},
|
|
402
405
|
"value": {"type": "string", "title": "Identifier Value"},
|
|
403
406
|
"country": {"type": "string", "title": "Country"},
|
|
404
407
|
"is_primary": {"type": "boolean", "default": False, "title": "Is Primary"},
|
|
@@ -408,7 +411,7 @@ class Migration(migrations.Migration):
|
|
|
408
411
|
"description": "Entity (Person/Organization/Trust) model.\n\nRepresents a company or organization in the CRM system.\nOwned by individual users with permission controls.",
|
|
409
412
|
},
|
|
410
413
|
},
|
|
411
|
-
"custom_code":
|
|
414
|
+
"custom_code": "@classmethod\ndef custom_fields_cell_template(cls) -> str:\n return 'JsonTemplate'\n\nasync def _avalidate_custom_fields(self) -> None:\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = await CustomFieldService.avalidate_custom_fields(self.__class__.__name__, self.custom_fields)\n\nasync def apre_create(self) -> None:\n await self._avalidate_custom_fields()\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n await self._avalidate_custom_fields()\n await super().apre_update()\n\ndef _validate_custom_fields(self) -> None:\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = CustomFieldService.validate_custom_fields(self.__class__.__name__, self.custom_fields)\n\ndef pre_create(self) -> None:\n self._validate_custom_fields()\n super().pre_create()\n\ndef pre_update(self) -> None:\n self._validate_custom_fields()\n super().pre_update()",
|
|
412
415
|
"storage_metadata": {
|
|
413
416
|
"table_name": "EntityIdentifier",
|
|
414
417
|
"db_fields": {"entity": ["entity_partition_key"]},
|
|
@@ -426,6 +429,7 @@ class Migration(migrations.Migration):
|
|
|
426
429
|
"properties": {
|
|
427
430
|
"created_at": {"type": "datetime", "title": "Created At", "format": "date-time"},
|
|
428
431
|
"updated_at": {"type": "datetime", "title": "Updated At", "format": "date-time"},
|
|
432
|
+
"custom_fields": {"type": "anything", "title": "Custom Fields"},
|
|
429
433
|
"start_date": {"type": "string", "title": "Start Date"},
|
|
430
434
|
"end_date": {"type": "string", "title": "End Date"},
|
|
431
435
|
"relationship_group_name": {"type": "string", "title": "Relationship Group Name"},
|
|
@@ -440,7 +444,7 @@ class Migration(migrations.Migration):
|
|
|
440
444
|
"description": "Entity (Person/Organization/Trust) model.\n\nRepresents a company or organization in the CRM system.\nOwned by individual users with permission controls.",
|
|
441
445
|
},
|
|
442
446
|
},
|
|
443
|
-
"custom_code":
|
|
447
|
+
"custom_code": "@classmethod\ndef custom_fields_cell_template(cls) -> str:\n return 'JsonTemplate'\n\nasync def _avalidate_custom_fields(self) -> None:\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = await CustomFieldService.avalidate_custom_fields(self.__class__.__name__, self.custom_fields)\n\nasync def apre_create(self) -> None:\n await self._avalidate_custom_fields()\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n await self._avalidate_custom_fields()\n await super().apre_update()\n\ndef _validate_custom_fields(self) -> None:\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = CustomFieldService.validate_custom_fields(self.__class__.__name__, self.custom_fields)\n\ndef pre_create(self) -> None:\n self._validate_custom_fields()\n super().pre_create()\n\ndef pre_update(self) -> None:\n self._validate_custom_fields()\n super().pre_update()",
|
|
444
448
|
"storage_metadata": {
|
|
445
449
|
"table_name": "EntityRelationship",
|
|
446
450
|
"db_fields": {
|
|
@@ -509,6 +513,7 @@ class Migration(migrations.Migration):
|
|
|
509
513
|
"properties": {
|
|
510
514
|
"created_at": {"type": "datetime", "title": "Created At", "format": "date-time"},
|
|
511
515
|
"updated_at": {"type": "datetime", "title": "Updated At", "format": "date-time"},
|
|
516
|
+
"custom_fields": {"type": "anything", "title": "Custom Fields"},
|
|
512
517
|
"name": {"type": "string", "title": "Deal Name"},
|
|
513
518
|
"amount": {"type": "number", "title": "Amount"},
|
|
514
519
|
"currency": {"type": "string", "default": "USD", "title": "Currency"},
|
|
@@ -535,10 +540,9 @@ class Migration(migrations.Migration):
|
|
|
535
540
|
"title": "Status",
|
|
536
541
|
"enum": ["open", "closed_won", "closed_lost"],
|
|
537
542
|
},
|
|
538
|
-
"custom_fields": {"type": "anything", "title": "Custom Fields"},
|
|
539
543
|
"assigned_to": {"type": "User", "title": "Assigned To"},
|
|
540
544
|
},
|
|
541
|
-
"custom_code": "import datetime as _dt\n\nfrom amsdal.contrib.auth.models.user import User\nfrom amsdal_utils.models.data_models.reference import Reference\n\n\n@property\ndef display_name(self) -> str:\n \"\"\"Return display name for the deal.\"\"\"\n return self.name\n\n@property\ndef stage_name(self) -> str:\n \"\"\"Returns stage name for display.\"\"\"\n if hasattr(self.stage, 'name'):\n return self.stage.name\n return str(self.stage)\n\nasync def
|
|
545
|
+
"custom_code": "import datetime as _dt\n\nfrom amsdal.contrib.auth.models.user import User\nfrom amsdal_utils.models.data_models.reference import Reference\n\n\n@classmethod\ndef custom_fields_cell_template(cls) -> str:\n return 'JsonTemplate'\n\n@property\ndef display_name(self) -> str:\n \"\"\"Return display name for the deal.\"\"\"\n return self.name\n\n@property\ndef stage_name(self) -> str:\n \"\"\"Returns stage name for display.\"\"\"\n if hasattr(self.stage, 'name'):\n return self.stage.name\n return str(self.stage)\n\nasync def _avalidate_custom_fields(self) -> None:\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = await CustomFieldService.avalidate_custom_fields(self.__class__.__name__, self.custom_fields)\n\nasync def apost_update(self) -> None:\n \"\"\"Async hook called after updating deal.\"\"\"\n from amsdal_crm.services.workflow_service import WorkflowService\n await WorkflowService.aexecute_rules('Deal', 'update', self)\n\nasync def apre_create(self) -> None:\n await self._avalidate_custom_fields()\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n \"\"\"Async hook called before updating deal.\n\n Automatically syncs is_closed and is_won status with stage,\n and sets closed_date when deal is closed.\n \"\"\"\n stage = await self.stage\n if stage.status == 'open':\n self.status = 'open'\n if stage.status == 'closed_won':\n self.status = 'closed_won'\n if stage.status == 'closed_lost':\n self.status = 'closed_lost'\n if self.status in ('closed_won', 'closed_lost') and (not self.closed_date):\n self.closed_date = _dt.datetime.now(_dt.UTC)\n await super().apre_update()\n\ndef _validate_custom_fields(self) -> None:\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = CustomFieldService.validate_custom_fields(self.__class__.__name__, self.custom_fields)\n\ndef has_object_permission(self, user: 'User', action: str) -> bool:\n \"\"\"Check if user has permission to perform action on this deal.\n\n Args:\n user: The user attempting the action\n action: The action being attempted (read, create, update, delete)\n\n Returns:\n True if user has permission, False otherwise\n \"\"\"\n if self.assigned_to and self.assigned_to.email == user.email:\n return True\n if user.permissions:\n for permission in user.permissions:\n if permission.model == '*' and permission.action in ('*', action):\n return True\n if permission.model == 'Deal' and permission.action in ('*', action):\n return True\n return False\n\ndef post_update(self) -> None:\n \"\"\"Hook called after updating deal.\"\"\"\n from amsdal_crm.services.workflow_service import WorkflowService\n WorkflowService.execute_rules('Deal', 'update', self)\n\ndef pre_create(self) -> None:\n self._validate_custom_fields()\n super().pre_create()\n\ndef pre_update(self) -> None:\n \"\"\"Hook called before updating deal.\n\n Automatically syncs is_closed and is_won status with stage,\n and sets closed_date when deal is closed.\n \"\"\"\n from amsdal_models.classes.helpers.reference_loader import ReferenceLoader\n stage = ReferenceLoader(self.stage).load_reference() if isinstance(self.stage, Reference) else self.stage\n if stage.status == 'open':\n self.status = 'open'\n if stage.status == 'closed_won':\n self.status = 'closed_won'\n if stage.status == 'closed_lost':\n self.status = 'closed_lost'\n if self.status in ('closed_won', 'closed_lost') and (not self.closed_date):\n self.closed_date = _dt.datetime.now(_dt.UTC)\n super().pre_update()",
|
|
542
546
|
"storage_metadata": {
|
|
543
547
|
"table_name": "Deal",
|
|
544
548
|
"db_fields": {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from pydantic.fields import Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CustomFieldsMixin:
|
|
7
|
+
custom_fields: dict[str, Any] | None = Field(default=None, title='Custom Fields')
|
|
8
|
+
|
|
9
|
+
@classmethod
|
|
10
|
+
def custom_fields_cell_template(cls) -> str:
|
|
11
|
+
return 'JsonTemplate'
|
|
12
|
+
|
|
13
|
+
def pre_create(self) -> None:
|
|
14
|
+
self._validate_custom_fields()
|
|
15
|
+
super().pre_create()
|
|
16
|
+
|
|
17
|
+
async def apre_create(self) -> None:
|
|
18
|
+
await self._avalidate_custom_fields()
|
|
19
|
+
await super().apre_create()
|
|
20
|
+
|
|
21
|
+
def pre_update(self) -> None:
|
|
22
|
+
self._validate_custom_fields()
|
|
23
|
+
super().pre_update()
|
|
24
|
+
|
|
25
|
+
async def apre_update(self) -> None:
|
|
26
|
+
await self._avalidate_custom_fields()
|
|
27
|
+
await super().apre_update()
|
|
28
|
+
|
|
29
|
+
def _validate_custom_fields(self) -> None:
|
|
30
|
+
if self.custom_fields:
|
|
31
|
+
from amsdal_crm.services.custom_field_service import CustomFieldService
|
|
32
|
+
|
|
33
|
+
self.custom_fields = CustomFieldService.validate_custom_fields(
|
|
34
|
+
self.__class__.__name__,
|
|
35
|
+
self.custom_fields,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
async def _avalidate_custom_fields(self) -> None:
|
|
39
|
+
if self.custom_fields:
|
|
40
|
+
from amsdal_crm.services.custom_field_service import CustomFieldService
|
|
41
|
+
|
|
42
|
+
self.custom_fields = await CustomFieldService.avalidate_custom_fields(
|
|
43
|
+
self.__class__.__name__,
|
|
44
|
+
self.custom_fields,
|
|
45
|
+
)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Deal Model."""
|
|
2
2
|
|
|
3
3
|
import datetime as _dt
|
|
4
|
-
from typing import Any
|
|
5
4
|
from typing import ClassVar
|
|
6
5
|
from typing import Literal
|
|
7
6
|
|
|
@@ -14,13 +13,15 @@ from amsdal_utils.models.data_models.reference import Reference
|
|
|
14
13
|
from amsdal_utils.models.enums import ModuleType
|
|
15
14
|
from pydantic.fields import Field
|
|
16
15
|
|
|
16
|
+
from models.common import CustomFieldsMixin
|
|
17
|
+
|
|
17
18
|
|
|
18
19
|
class DealManager(Manager):
|
|
19
20
|
def get_queryset(self) -> 'DealManager':
|
|
20
21
|
return super().get_queryset().select_related('stage')
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
class Deal(TimestampMixin, Model):
|
|
24
|
+
class Deal(CustomFieldsMixin, TimestampMixin, Model):
|
|
24
25
|
"""Deal (Sales Opportunity) model.
|
|
25
26
|
|
|
26
27
|
Represents a sales opportunity linked to an account and contact,
|
|
@@ -50,9 +51,6 @@ class Deal(TimestampMixin, Model):
|
|
|
50
51
|
# Status tracking
|
|
51
52
|
status: Literal['open', 'closed_won', 'closed_lost'] = Field(default='open', title='Status')
|
|
52
53
|
|
|
53
|
-
# Custom fields (JSON)
|
|
54
|
-
custom_fields: dict[str, Any] | None = Field(default=None, title='Custom Fields')
|
|
55
|
-
|
|
56
54
|
@property
|
|
57
55
|
def display_name(self) -> str:
|
|
58
56
|
"""Return display name for the deal."""
|
|
@@ -90,34 +88,12 @@ class Deal(TimestampMixin, Model):
|
|
|
90
88
|
|
|
91
89
|
return False
|
|
92
90
|
|
|
93
|
-
def pre_create(self) -> None:
|
|
94
|
-
"""Hook called before creating deal."""
|
|
95
|
-
if self.custom_fields:
|
|
96
|
-
from amsdal_crm.services.custom_field_service import CustomFieldService
|
|
97
|
-
|
|
98
|
-
self.custom_fields = CustomFieldService.validate_custom_fields('Deal', self.custom_fields)
|
|
99
|
-
super().pre_create()
|
|
100
|
-
|
|
101
|
-
async def apre_create(self) -> None:
|
|
102
|
-
"""Async hook called before creating deal."""
|
|
103
|
-
if self.custom_fields:
|
|
104
|
-
from amsdal_crm.services.custom_field_service import CustomFieldService
|
|
105
|
-
|
|
106
|
-
self.custom_fields = await CustomFieldService.avalidate_custom_fields('Deal', self.custom_fields)
|
|
107
|
-
await super().apre_create()
|
|
108
|
-
|
|
109
91
|
def pre_update(self) -> None:
|
|
110
92
|
"""Hook called before updating deal.
|
|
111
93
|
|
|
112
94
|
Automatically syncs is_closed and is_won status with stage,
|
|
113
95
|
and sets closed_date when deal is closed.
|
|
114
96
|
"""
|
|
115
|
-
# Validate custom fields first
|
|
116
|
-
if self.custom_fields:
|
|
117
|
-
from amsdal_crm.services.custom_field_service import CustomFieldService
|
|
118
|
-
|
|
119
|
-
self.custom_fields = CustomFieldService.validate_custom_fields('Deal', self.custom_fields)
|
|
120
|
-
|
|
121
97
|
# Load stage if it's a reference and sync closed status
|
|
122
98
|
from amsdal_models.classes.helpers.reference_loader import ReferenceLoader
|
|
123
99
|
|
|
@@ -142,14 +118,7 @@ class Deal(TimestampMixin, Model):
|
|
|
142
118
|
|
|
143
119
|
Automatically syncs is_closed and is_won status with stage,
|
|
144
120
|
and sets closed_date when deal is closed.
|
|
145
|
-
"""
|
|
146
|
-
# Validate custom fields first
|
|
147
|
-
if self.custom_fields:
|
|
148
|
-
from amsdal_crm.services.custom_field_service import CustomFieldService
|
|
149
|
-
|
|
150
|
-
self.custom_fields = await CustomFieldService.avalidate_custom_fields('Deal', self.custom_fields)
|
|
151
|
-
|
|
152
|
-
# Load stage if it's a reference and sync closed status
|
|
121
|
+
""" # Load stage if it's a reference and sync closed status
|
|
153
122
|
|
|
154
123
|
stage = await self.stage
|
|
155
124
|
if stage.status == 'open':
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Account Model."""
|
|
2
|
+
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
from amsdal.contrib.auth.models.user import User
|
|
7
|
+
from amsdal.models.mixins import TimestampMixin
|
|
8
|
+
from amsdal_models.classes.data_models.constraints import UniqueConstraint
|
|
9
|
+
from amsdal_models.classes.data_models.indexes import IndexInfo
|
|
10
|
+
from amsdal_models.classes.model import Model
|
|
11
|
+
from amsdal_utils.models.enums import ModuleType
|
|
12
|
+
from pydantic.fields import Field
|
|
13
|
+
|
|
14
|
+
from models.common import CustomFieldsMixin
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Entity(CustomFieldsMixin, TimestampMixin, Model):
|
|
18
|
+
"""Entity (Person/Organization/Trust) model.
|
|
19
|
+
|
|
20
|
+
Represents a company or organization in the CRM system.
|
|
21
|
+
Owned by individual users with permission controls.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
__module_type__: ClassVar[ModuleType] = ModuleType.CONTRIB
|
|
25
|
+
__constraints__: ClassVar[list[UniqueConstraint]] = [UniqueConstraint(name='unq_entity_name', fields=['name'])]
|
|
26
|
+
__indexes__: ClassVar[list[IndexInfo]] = [
|
|
27
|
+
IndexInfo(name='idx_entity_created_at', field='created_at'),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
# Core fields
|
|
31
|
+
name: str = Field(title='Entity Name')
|
|
32
|
+
legal_name: str | None = Field(default=None, title='Legal Name')
|
|
33
|
+
status: Literal['Active', 'Inactive'] = Field(default='Active', title='Status')
|
|
34
|
+
note: str | None = Field(default=None, title='Note')
|
|
35
|
+
|
|
36
|
+
assigned_to: User | None = Field(default=None, title='Assigned To')
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def display_name(self) -> str:
|
|
40
|
+
"""Return display name for the account."""
|
|
41
|
+
return self.name
|
|
42
|
+
|
|
43
|
+
def has_object_permission(self, user: 'User', action: str) -> bool:
|
|
44
|
+
"""Check if user has permission to perform action on this account.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
user: The user attempting the action
|
|
48
|
+
action: The action being attempted (read, create, update, delete)
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
True if user has permission, False otherwise
|
|
52
|
+
"""
|
|
53
|
+
if self.assigned_to and self.assigned_to.email == user.email:
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
# Check admin permissions
|
|
57
|
+
if user.permissions:
|
|
58
|
+
for permission in user.permissions:
|
|
59
|
+
if permission.model == '*' and permission.action in ('*', action):
|
|
60
|
+
return True
|
|
61
|
+
if permission.model == 'Entity' and permission.action in ('*', action):
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
def post_update(self) -> None:
|
|
67
|
+
"""Hook called after updating account."""
|
|
68
|
+
from amsdal_crm.services.workflow_service import WorkflowService
|
|
69
|
+
|
|
70
|
+
WorkflowService.execute_rules('Entity', 'update', self)
|
|
71
|
+
|
|
72
|
+
async def apost_update(self) -> None:
|
|
73
|
+
"""Async hook called after updating account."""
|
|
74
|
+
from amsdal_crm.services.workflow_service import WorkflowService
|
|
75
|
+
|
|
76
|
+
await WorkflowService.aexecute_rules('Entity', 'update', self)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class EntityRelationship(CustomFieldsMixin, TimestampMixin, Model):
|
|
80
|
+
__module_type__: ClassVar[ModuleType] = ModuleType.CONTRIB
|
|
81
|
+
|
|
82
|
+
from_entity: Entity = Field(title='From Entity')
|
|
83
|
+
to_entity: Entity = Field(title='To Entity')
|
|
84
|
+
start_date: str | None = Field(default=None, title='Start Date')
|
|
85
|
+
end_date: str | None = Field(default=None, title='End Date')
|
|
86
|
+
relationship_group_name: str | None = Field(default=None, title='Relationship Group Name')
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class EntityIdentifier(CustomFieldsMixin, TimestampMixin, Model):
|
|
90
|
+
__module_type__: ClassVar[ModuleType] = ModuleType.CONTRIB
|
|
91
|
+
|
|
92
|
+
entity: Entity = Field(title='Entity')
|
|
93
|
+
value: str = Field(title='Identifier Value')
|
|
94
|
+
country: str | None = Field(default=None, title='Country')
|
|
95
|
+
|
|
96
|
+
# TODO: validate one per entity
|
|
97
|
+
is_primary: bool = Field(default=False, title='Is Primary')
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class EntityContactPoint(CustomFieldsMixin, TimestampMixin, Model):
|
|
101
|
+
__module_type__: ClassVar[ModuleType] = ModuleType.CONTRIB
|
|
102
|
+
|
|
103
|
+
entity: Entity = Field(title='Entity')
|
|
104
|
+
value: str = Field(title='Contact Point Value')
|
|
105
|
+
|
|
106
|
+
# TODO: validate one per entity
|
|
107
|
+
is_primary: bool = Field(default=False, title='Is Primary')
|
|
108
|
+
can_contact: bool = Field(default=True, title='Can Contact')
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class EntityAddress(CustomFieldsMixin, TimestampMixin, Model):
|
|
112
|
+
__module_type__: ClassVar[ModuleType] = ModuleType.CONTRIB
|
|
113
|
+
|
|
114
|
+
line1: str | None = Field(title='Address Line 1')
|
|
115
|
+
line2: str | None = Field(default=None, title='Address Line 2')
|
|
116
|
+
city: str | None = Field(title='City')
|
|
117
|
+
region: str | None = Field(default=None, title='Region/State')
|
|
118
|
+
postal_code: str | None = Field(default=None, title='Postal Code')
|
|
119
|
+
country: str | None = Field(title='Country')
|
|
120
|
+
|
|
121
|
+
# TODO: validate one per entity
|
|
122
|
+
is_primary: bool = Field(default=False, title='Is Primary')
|