amsdal_crm 0.1.9__py3-none-any.whl → 0.2.0__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.
@@ -4,45 +4,12 @@ from amsdal_utils.models.enums import ModuleType
4
4
 
5
5
  class Migration(migrations.Migration):
6
6
  operations: list[migrations.Operation] = [
7
- migrations.CreateClass(
8
- module_type=ModuleType.CONTRIB,
9
- class_name="Account",
10
- new_schema={
11
- "title": "Account",
12
- "required": ["name", "owner_email"],
13
- "properties": {
14
- "created_at": {"type": "datetime", "title": "Created At", "format": "date-time"},
15
- "updated_at": {"type": "datetime", "title": "Updated At", "format": "date-time"},
16
- "name": {"type": "string", "title": "Account Name"},
17
- "website": {"type": "string", "title": "Website"},
18
- "phone": {"type": "string", "title": "Phone"},
19
- "industry": {"type": "string", "title": "Industry"},
20
- "billing_street": {"type": "string", "title": "Billing Street"},
21
- "billing_city": {"type": "string", "title": "Billing City"},
22
- "billing_state": {"type": "string", "title": "Billing State"},
23
- "billing_postal_code": {"type": "string", "title": "Billing Postal Code"},
24
- "billing_country": {"type": "string", "title": "Billing Country"},
25
- "owner_email": {"type": "string", "title": "Owner Email"},
26
- "custom_fields": {"type": "anything", "title": "Custom Fields"},
27
- },
28
- "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 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(\'Account\', \'update\', self)\n\nasync def apre_create(self) -> None:\n """Async hook called before creating account."""\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = await CustomFieldService.avalidate_custom_fields(\'Account\', self.custom_fields)\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n """Async hook called before updating account."""\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = await CustomFieldService.avalidate_custom_fields(\'Account\', self.custom_fields)\n await super().apre_update()\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.owner_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 == \'Account\' 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(\'Account\', \'update\', self)\n\ndef pre_create(self) -> None:\n """Hook called before creating account."""\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = CustomFieldService.validate_custom_fields(\'Account\', self.custom_fields)\n super().pre_create()\n\ndef pre_update(self) -> None:\n """Hook called before updating account."""\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = CustomFieldService.validate_custom_fields(\'Account\', self.custom_fields)\n super().pre_update()',
29
- "storage_metadata": {
30
- "table_name": "Account",
31
- "db_fields": {},
32
- "primary_key": ["partition_key"],
33
- "indexed": [["owner_email"], ["created_at"]],
34
- "unique": [["name", "owner_email"]],
35
- "foreign_keys": {},
36
- },
37
- "description": "Account (Company/Organization) model.\n\nRepresents a company or organization in the CRM system.\nOwned by individual users with permission controls.",
38
- },
39
- ),
40
7
  migrations.CreateClass(
41
8
  module_type=ModuleType.CONTRIB,
42
9
  class_name="Activity",
43
10
  new_schema={
44
11
  "title": "Activity",
45
- "required": ["activity_type", "subject", "related_to_type", "related_to_id", "owner_email"],
12
+ "required": ["activity_type", "subject"],
46
13
  "properties": {
47
14
  "created_at": {"type": "datetime", "title": "Created At", "format": "date-time"},
48
15
  "updated_at": {"type": "datetime", "title": "Updated At", "format": "date-time"},
@@ -63,89 +30,88 @@ class Migration(migrations.Migration):
63
30
  "subject": {"type": "string", "title": "Subject"},
64
31
  "description": {"type": "string", "title": "Description"},
65
32
  "related_to_type": {
66
- "type": "ActivityRelatedTo",
67
- "options": [
68
- {"key": "CONTACT", "value": "Contact"},
69
- {"key": "ACCOUNT", "value": "Account"},
70
- {"key": "DEAL", "value": "Deal"},
71
- ],
72
- "title": "ActivityRelatedTo",
33
+ "type": "string",
34
+ "options": [{"key": "ENTITY", "value": "Entity"}, {"key": "DEAL", "value": "Deal"}],
35
+ "title": "Related To Type",
73
36
  "description": "What type of record this activity is related to.",
74
- "enum": ["Contact", "Account", "Deal"],
75
- "x_enum_names": ["CONTACT", "ACCOUNT", "DEAL"],
37
+ "enum": ["Entity", "Deal"],
38
+ "x_enum_names": ["ENTITY", "DEAL"],
76
39
  },
77
40
  "related_to_id": {"type": "string", "title": "Related To ID"},
78
- "owner_email": {"type": "string", "title": "Owner Email"},
79
41
  "due_date": {"type": "datetime", "title": "Due Date", "format": "date-time"},
80
42
  "completed_at": {"type": "datetime", "title": "Completed At", "format": "date-time"},
81
43
  "is_completed": {"type": "boolean", "default": False, "title": "Is Completed"},
44
+ "assigned_to": {"type": "User", "title": "Assigned To"},
82
45
  },
83
- "custom_code": "import datetime as _dt\n\nfrom amsdal.contrib.auth.models.user import User\n\n\n@property\ndef display_name(self) -> str:\n \"\"\"Return display name for the activity.\"\"\"\n return f'{self.activity_type.value}: {self.subject}'\n\nasync def apre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = await self.aget_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n await super().apre_update()\n\ndef has_object_permission(self, user: 'User', action: str) -> bool:\n \"\"\"Check if user has permission to perform action on this activity.\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.owner_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 == 'Activity' and permission.action in ('*', action):\n return True\n return False\n\ndef pre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n super().pre_create()\n\ndef pre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = self.get_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n super().pre_update()",
46
+ "custom_code": "import datetime as _dt\n\nfrom amsdal.contrib.auth.models.user import User\n\n\n@property\ndef display_name(self) -> str:\n \"\"\"Return display name for the activity.\"\"\"\n return f'{self.activity_type.value}: {self.subject}'\n\nasync def apre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = await self.aget_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n await super().apre_update()\n\ndef has_object_permission(self, user: 'User', action: str) -> bool:\n \"\"\"Check if user has permission to perform action on this activity.\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 == 'Activity' and permission.action in ('*', action):\n return True\n return False\n\ndef pre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n super().pre_create()\n\ndef pre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = self.get_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n super().pre_update()",
84
47
  "storage_metadata": {
85
48
  "table_name": "Activity",
86
- "db_fields": {},
49
+ "db_fields": {"assigned_to": ["assigned_to_partition_key"]},
87
50
  "primary_key": ["partition_key"],
88
- "indexed": [["related_to_id"], ["owner_email"], ["created_at"], ["due_date"]],
89
- "foreign_keys": {},
51
+ "indexed": [["related_to_id"], ["created_at"], ["due_date"]],
52
+ "foreign_keys": {
53
+ "assigned_to": [{"assigned_to_partition_key": "string"}, "User", ["partition_key"]]
54
+ },
90
55
  },
91
56
  "description": "Base activity model with polymorphic related_to field.\n\nActivities can be linked to Contacts, Accounts, or Deals using\na generic foreign key pattern (related_to_type + related_to_id).",
92
57
  },
93
58
  ),
94
59
  migrations.CreateClass(
95
60
  module_type=ModuleType.CONTRIB,
96
- class_name="Pipeline",
61
+ class_name="Entity",
97
62
  new_schema={
98
- "title": "Pipeline",
63
+ "title": "Entity",
99
64
  "required": ["name"],
100
65
  "properties": {
101
- "name": {"type": "string", "title": "Pipeline Name"},
102
- "description": {"type": "string", "title": "Description"},
103
- "is_active": {"type": "boolean", "default": True, "title": "Is Active"},
66
+ "created_at": {"type": "datetime", "title": "Created At", "format": "date-time"},
67
+ "updated_at": {"type": "datetime", "title": "Updated At", "format": "date-time"},
68
+ "name": {"type": "string", "title": "Entity Name"},
69
+ "legal_name": {"type": "string", "title": "Legal Name"},
70
+ "status": {
71
+ "type": "string",
72
+ "default": "Active",
73
+ "options": [{"key": "Active", "value": "Active"}, {"key": "Inactive", "value": "Inactive"}],
74
+ "title": "Status",
75
+ "enum": ["Active", "Inactive"],
76
+ },
77
+ "note": {"type": "string", "title": "Note"},
78
+ "custom_fields": {"type": "anything", "title": "Custom Fields"},
79
+ "assigned_to": {"type": "User", "title": "Assigned To"},
104
80
  },
105
- "custom_code": '@property\ndef display_name(self) -> str:\n """Return display name for the pipeline."""\n return self.name',
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 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 """Async hook called before creating account."""\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = await CustomFieldService.avalidate_custom_fields(\'Entity\', self.custom_fields)\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n """Async hook called before updating account."""\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = await CustomFieldService.avalidate_custom_fields(\'Entity\', self.custom_fields)\n await super().apre_update()\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 """Hook called before creating account."""\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = CustomFieldService.validate_custom_fields(\'Entity\', self.custom_fields)\n super().pre_create()\n\ndef pre_update(self) -> None:\n """Hook called before updating account."""\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = CustomFieldService.validate_custom_fields(\'Entity\', self.custom_fields)\n super().pre_update()',
106
82
  "storage_metadata": {
107
- "table_name": "Pipeline",
108
- "db_fields": {},
83
+ "table_name": "Entity",
84
+ "db_fields": {"assigned_to": ["assigned_to_partition_key"]},
109
85
  "primary_key": ["partition_key"],
86
+ "indexed": [["created_at"]],
110
87
  "unique": [["name"]],
111
- "foreign_keys": {},
88
+ "foreign_keys": {
89
+ "assigned_to": [{"assigned_to_partition_key": "string"}, "User", ["partition_key"]]
90
+ },
112
91
  },
113
- "description": "Sales pipeline model.\n\nRepresents a sales pipeline with multiple stages.\nPipelines are system-wide and not owned by individual users.",
92
+ "description": "Entity (Person/Organization/Trust) model.\n\nRepresents a company or organization in the CRM system.\nOwned by individual users with permission controls.",
114
93
  },
115
94
  ),
116
95
  migrations.CreateClass(
117
96
  module_type=ModuleType.CONTRIB,
118
- class_name="Contact",
97
+ class_name="Pipeline",
119
98
  new_schema={
120
- "title": "Contact",
121
- "required": ["first_name", "last_name", "email", "owner_email"],
99
+ "title": "Pipeline",
100
+ "required": ["name"],
122
101
  "properties": {
123
- "created_at": {"type": "datetime", "title": "Created At", "format": "date-time"},
124
- "updated_at": {"type": "datetime", "title": "Updated At", "format": "date-time"},
125
- "first_name": {"type": "string", "title": "First Name"},
126
- "last_name": {"type": "string", "title": "Last Name"},
127
- "email": {"type": "string", "title": "Email"},
128
- "phone": {"type": "string", "title": "Phone Number"},
129
- "mobile": {"type": "string", "title": "Mobile Number"},
130
- "title": {"type": "string", "title": "Job Title"},
131
- "account": {
132
- "type": "Account",
133
- "title": "Account",
134
- "description": "Account (Company/Organization) model.\n\nRepresents a company or organization in the CRM system.\nOwned by individual users with permission controls.",
135
- },
136
- "owner_email": {"type": "string", "title": "Owner Email"},
137
- "custom_fields": {"type": "anything", "title": "Custom Fields"},
102
+ "name": {"type": "string", "title": "Pipeline Name"},
103
+ "description": {"type": "string", "title": "Description"},
104
+ "is_active": {"type": "boolean", "default": True, "title": "Is Active"},
138
105
  },
139
- "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 contact."""\n return f\'{self.first_name} {self.last_name}\'\n\n@property\ndef full_name(self) -> str:\n """Return full name of the contact."""\n return f\'{self.first_name} {self.last_name}\'\n\nasync def apost_update(self) -> None:\n """Async hook called after updating contact."""\n from amsdal_crm.services.workflow_service import WorkflowService\n await WorkflowService.aexecute_rules(\'Contact\', \'update\', self)\n\nasync def apre_create(self) -> None:\n """Async hook called before creating contact."""\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = await CustomFieldService.avalidate_custom_fields(\'Contact\', self.custom_fields)\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n """Async hook called before updating contact."""\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = await CustomFieldService.avalidate_custom_fields(\'Contact\', self.custom_fields)\n await super().apre_update()\n\ndef has_object_permission(self, user: \'User\', action: str) -> bool:\n """Check if user has permission to perform action on this contact.\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.owner_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 == \'Contact\' and permission.action in (\'*\', action):\n return True\n return False\n\ndef post_update(self) -> None:\n """Hook called after updating contact."""\n from amsdal_crm.services.workflow_service import WorkflowService\n WorkflowService.execute_rules(\'Contact\', \'update\', self)\n\ndef pre_create(self) -> None:\n """Hook called before creating contact."""\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = CustomFieldService.validate_custom_fields(\'Contact\', self.custom_fields)\n super().pre_create()\n\ndef pre_update(self) -> None:\n """Hook called before updating contact."""\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = CustomFieldService.validate_custom_fields(\'Contact\', self.custom_fields)\n super().pre_update()',
106
+ "custom_code": '@property\ndef display_name(self) -> str:\n """Return display name for the pipeline."""\n return self.name',
140
107
  "storage_metadata": {
141
- "table_name": "Contact",
142
- "db_fields": {"account": ["account_partition_key"]},
108
+ "table_name": "Pipeline",
109
+ "db_fields": {},
143
110
  "primary_key": ["partition_key"],
144
- "indexed": [["owner_email"], ["created_at"]],
145
- "unique": [["email"]],
146
- "foreign_keys": {"account": [{"account_partition_key": "string"}, "Account", ["partition_key"]]},
111
+ "unique": [["name"]],
112
+ "foreign_keys": {},
147
113
  },
148
- "description": "Contact (Person) model.\n\nRepresents a person in the CRM system, optionally linked to an Account.\nOwned by individual users with permission controls.",
114
+ "description": "Sales pipeline model.\n\nRepresents a sales pipeline with multiple stages.\nPipelines are system-wide and not owned by individual users.",
149
115
  },
150
116
  ),
151
117
  migrations.CreateClass(
@@ -154,7 +120,7 @@ class Migration(migrations.Migration):
154
120
  new_schema={
155
121
  "title": "Call",
156
122
  "type": "Activity",
157
- "required": ["subject", "related_to_type", "related_to_id", "owner_email", "phone_number"],
123
+ "required": ["subject", "phone_number"],
158
124
  "properties": {
159
125
  "created_at": {"type": "datetime", "title": "Created At", "format": "date-time"},
160
126
  "updated_at": {"type": "datetime", "title": "Updated At", "format": "date-time"},
@@ -162,32 +128,28 @@ class Migration(migrations.Migration):
162
128
  "subject": {"type": "string", "title": "Subject"},
163
129
  "description": {"type": "string", "title": "Description"},
164
130
  "related_to_type": {
165
- "type": "ActivityRelatedTo",
166
- "options": [
167
- {"key": "CONTACT", "value": "Contact"},
168
- {"key": "ACCOUNT", "value": "Account"},
169
- {"key": "DEAL", "value": "Deal"},
170
- ],
171
- "title": "ActivityRelatedTo",
131
+ "type": "string",
132
+ "options": [{"key": "ENTITY", "value": "Entity"}, {"key": "DEAL", "value": "Deal"}],
133
+ "title": "Related To Type",
172
134
  "description": "What type of record this activity is related to.",
173
- "enum": ["Contact", "Account", "Deal"],
174
- "x_enum_names": ["CONTACT", "ACCOUNT", "DEAL"],
135
+ "enum": ["Entity", "Deal"],
136
+ "x_enum_names": ["ENTITY", "DEAL"],
175
137
  },
176
138
  "related_to_id": {"type": "string", "title": "Related To ID"},
177
- "owner_email": {"type": "string", "title": "Owner Email"},
178
139
  "due_date": {"type": "datetime", "title": "Due Date", "format": "date-time"},
179
140
  "completed_at": {"type": "datetime", "title": "Completed At", "format": "date-time"},
180
141
  "is_completed": {"type": "boolean", "default": False, "title": "Is Completed"},
142
+ "assigned_to": {"type": "User", "title": "Assigned To"},
181
143
  "phone_number": {"type": "string", "title": "Phone Number"},
182
144
  "duration_seconds": {"type": "integer", "title": "Duration (seconds)"},
183
145
  "call_outcome": {"type": "string", "title": "Call Outcome"},
184
146
  },
185
- "custom_code": "import datetime as _dt\n\nfrom amsdal.contrib.auth.models.user import User\n\n\n@property\ndef display_name(self) -> str:\n \"\"\"Return display name for the activity.\"\"\"\n return f'{self.activity_type.value}: {self.subject}'\n\nasync def apre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = await self.aget_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n await super().apre_update()\n\ndef has_object_permission(self, user: 'User', action: str) -> bool:\n \"\"\"Check if user has permission to perform action on this activity.\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.owner_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 == 'Activity' and permission.action in ('*', action):\n return True\n return False\n\ndef pre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n super().pre_create()\n\ndef pre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = self.get_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n super().pre_update()",
147
+ "custom_code": "import datetime as _dt\n\nfrom amsdal.contrib.auth.models.user import User\n\n\n@property\ndef display_name(self) -> str:\n \"\"\"Return display name for the activity.\"\"\"\n return f'{self.activity_type.value}: {self.subject}'\n\nasync def apre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = await self.aget_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n await super().apre_update()\n\ndef has_object_permission(self, user: 'User', action: str) -> bool:\n \"\"\"Check if user has permission to perform action on this activity.\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 == 'Activity' and permission.action in ('*', action):\n return True\n return False\n\ndef pre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n super().pre_create()\n\ndef pre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = self.get_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n super().pre_update()",
186
148
  "storage_metadata": {
187
149
  "table_name": "Call",
188
150
  "db_fields": {},
189
151
  "primary_key": ["partition_key"],
190
- "indexed": [["related_to_id"], ["owner_email"], ["created_at"], ["due_date"]],
152
+ "indexed": [["related_to_id"], ["created_at"], ["due_date"]],
191
153
  "foreign_keys": {},
192
154
  },
193
155
  "description": "Phone call activity.",
@@ -199,15 +161,7 @@ class Migration(migrations.Migration):
199
161
  new_schema={
200
162
  "title": "EmailActivity",
201
163
  "type": "Activity",
202
- "required": [
203
- "subject",
204
- "related_to_type",
205
- "related_to_id",
206
- "owner_email",
207
- "from_address",
208
- "to_addresses",
209
- "body",
210
- ],
164
+ "required": ["subject", "from_address", "to_addresses", "body"],
211
165
  "properties": {
212
166
  "created_at": {"type": "datetime", "title": "Created At", "format": "date-time"},
213
167
  "updated_at": {"type": "datetime", "title": "Updated At", "format": "date-time"},
@@ -215,34 +169,30 @@ class Migration(migrations.Migration):
215
169
  "subject": {"type": "string", "title": "Subject"},
216
170
  "description": {"type": "string", "title": "Description"},
217
171
  "related_to_type": {
218
- "type": "ActivityRelatedTo",
219
- "options": [
220
- {"key": "CONTACT", "value": "Contact"},
221
- {"key": "ACCOUNT", "value": "Account"},
222
- {"key": "DEAL", "value": "Deal"},
223
- ],
224
- "title": "ActivityRelatedTo",
172
+ "type": "string",
173
+ "options": [{"key": "ENTITY", "value": "Entity"}, {"key": "DEAL", "value": "Deal"}],
174
+ "title": "Related To Type",
225
175
  "description": "What type of record this activity is related to.",
226
- "enum": ["Contact", "Account", "Deal"],
227
- "x_enum_names": ["CONTACT", "ACCOUNT", "DEAL"],
176
+ "enum": ["Entity", "Deal"],
177
+ "x_enum_names": ["ENTITY", "DEAL"],
228
178
  },
229
179
  "related_to_id": {"type": "string", "title": "Related To ID"},
230
- "owner_email": {"type": "string", "title": "Owner Email"},
231
180
  "due_date": {"type": "datetime", "title": "Due Date", "format": "date-time"},
232
181
  "completed_at": {"type": "datetime", "title": "Completed At", "format": "date-time"},
233
182
  "is_completed": {"type": "boolean", "default": False, "title": "Is Completed"},
183
+ "assigned_to": {"type": "User", "title": "Assigned To"},
234
184
  "from_address": {"type": "string", "title": "From Address"},
235
185
  "to_addresses": {"type": "array", "items": {"type": "string"}, "title": "To Addresses"},
236
186
  "cc_addresses": {"type": "array", "items": {"type": "string"}, "title": "CC Addresses"},
237
187
  "body": {"type": "string", "title": "Email Body"},
238
188
  "is_outbound": {"type": "boolean", "default": True, "title": "Is Outbound"},
239
189
  },
240
- "custom_code": "import datetime as _dt\n\nfrom amsdal.contrib.auth.models.user import User\n\n\n@property\ndef display_name(self) -> str:\n \"\"\"Return display name for the activity.\"\"\"\n return f'{self.activity_type.value}: {self.subject}'\n\nasync def apre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = await self.aget_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n await super().apre_update()\n\ndef has_object_permission(self, user: 'User', action: str) -> bool:\n \"\"\"Check if user has permission to perform action on this activity.\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.owner_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 == 'Activity' and permission.action in ('*', action):\n return True\n return False\n\ndef pre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n super().pre_create()\n\ndef pre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = self.get_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n super().pre_update()",
190
+ "custom_code": "import datetime as _dt\n\nfrom amsdal.contrib.auth.models.user import User\n\n\n@property\ndef display_name(self) -> str:\n \"\"\"Return display name for the activity.\"\"\"\n return f'{self.activity_type.value}: {self.subject}'\n\nasync def apre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = await self.aget_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n await super().apre_update()\n\ndef has_object_permission(self, user: 'User', action: str) -> bool:\n \"\"\"Check if user has permission to perform action on this activity.\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 == 'Activity' and permission.action in ('*', action):\n return True\n return False\n\ndef pre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n super().pre_create()\n\ndef pre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = self.get_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n super().pre_update()",
241
191
  "storage_metadata": {
242
192
  "table_name": "EmailActivity",
243
193
  "db_fields": {},
244
194
  "primary_key": ["partition_key"],
245
- "indexed": [["related_to_id"], ["owner_email"], ["created_at"], ["due_date"]],
195
+ "indexed": [["related_to_id"], ["created_at"], ["due_date"]],
246
196
  "foreign_keys": {},
247
197
  },
248
198
  "description": "Email activity with sender/recipients.",
@@ -254,7 +204,7 @@ class Migration(migrations.Migration):
254
204
  new_schema={
255
205
  "title": "Event",
256
206
  "type": "Activity",
257
- "required": ["subject", "related_to_type", "related_to_id", "owner_email", "start_time", "end_time"],
207
+ "required": ["subject", "start_time", "end_time"],
258
208
  "properties": {
259
209
  "created_at": {"type": "datetime", "title": "Created At", "format": "date-time"},
260
210
  "updated_at": {"type": "datetime", "title": "Updated At", "format": "date-time"},
@@ -262,32 +212,28 @@ class Migration(migrations.Migration):
262
212
  "subject": {"type": "string", "title": "Subject"},
263
213
  "description": {"type": "string", "title": "Description"},
264
214
  "related_to_type": {
265
- "type": "ActivityRelatedTo",
266
- "options": [
267
- {"key": "CONTACT", "value": "Contact"},
268
- {"key": "ACCOUNT", "value": "Account"},
269
- {"key": "DEAL", "value": "Deal"},
270
- ],
271
- "title": "ActivityRelatedTo",
215
+ "type": "string",
216
+ "options": [{"key": "ENTITY", "value": "Entity"}, {"key": "DEAL", "value": "Deal"}],
217
+ "title": "Related To Type",
272
218
  "description": "What type of record this activity is related to.",
273
- "enum": ["Contact", "Account", "Deal"],
274
- "x_enum_names": ["CONTACT", "ACCOUNT", "DEAL"],
219
+ "enum": ["Entity", "Deal"],
220
+ "x_enum_names": ["ENTITY", "DEAL"],
275
221
  },
276
222
  "related_to_id": {"type": "string", "title": "Related To ID"},
277
- "owner_email": {"type": "string", "title": "Owner Email"},
278
223
  "due_date": {"type": "datetime", "title": "Due Date", "format": "date-time"},
279
224
  "completed_at": {"type": "datetime", "title": "Completed At", "format": "date-time"},
280
225
  "is_completed": {"type": "boolean", "default": False, "title": "Is Completed"},
226
+ "assigned_to": {"type": "User", "title": "Assigned To"},
281
227
  "start_time": {"type": "datetime", "title": "Start Time", "format": "date-time"},
282
228
  "end_time": {"type": "datetime", "title": "End Time", "format": "date-time"},
283
229
  "location": {"type": "string", "title": "Location"},
284
230
  },
285
- "custom_code": "import datetime as _dt\n\nfrom amsdal.contrib.auth.models.user import User\n\n\n@property\ndef display_name(self) -> str:\n \"\"\"Return display name for the activity.\"\"\"\n return f'{self.activity_type.value}: {self.subject}'\n\nasync def apre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = await self.aget_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n await super().apre_update()\n\ndef has_object_permission(self, user: 'User', action: str) -> bool:\n \"\"\"Check if user has permission to perform action on this activity.\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.owner_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 == 'Activity' and permission.action in ('*', action):\n return True\n return False\n\ndef pre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n super().pre_create()\n\ndef pre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = self.get_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n super().pre_update()",
231
+ "custom_code": "import datetime as _dt\n\nfrom amsdal.contrib.auth.models.user import User\n\n\n@property\ndef display_name(self) -> str:\n \"\"\"Return display name for the activity.\"\"\"\n return f'{self.activity_type.value}: {self.subject}'\n\nasync def apre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = await self.aget_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n await super().apre_update()\n\ndef has_object_permission(self, user: 'User', action: str) -> bool:\n \"\"\"Check if user has permission to perform action on this activity.\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 == 'Activity' and permission.action in ('*', action):\n return True\n return False\n\ndef pre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n super().pre_create()\n\ndef pre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = self.get_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n super().pre_update()",
286
232
  "storage_metadata": {
287
233
  "table_name": "Event",
288
234
  "db_fields": {},
289
235
  "primary_key": ["partition_key"],
290
- "indexed": [["related_to_id"], ["owner_email"], ["created_at"], ["due_date"]],
236
+ "indexed": [["related_to_id"], ["created_at"], ["due_date"]],
291
237
  "foreign_keys": {},
292
238
  },
293
239
  "description": "Event/meeting activity with start/end times.",
@@ -299,7 +245,7 @@ class Migration(migrations.Migration):
299
245
  new_schema={
300
246
  "title": "Note",
301
247
  "type": "Activity",
302
- "required": ["subject", "related_to_type", "related_to_id", "owner_email"],
248
+ "required": ["subject"],
303
249
  "properties": {
304
250
  "created_at": {"type": "datetime", "title": "Created At", "format": "date-time"},
305
251
  "updated_at": {"type": "datetime", "title": "Updated At", "format": "date-time"},
@@ -307,29 +253,25 @@ class Migration(migrations.Migration):
307
253
  "subject": {"type": "string", "title": "Subject"},
308
254
  "description": {"type": "string", "title": "Description"},
309
255
  "related_to_type": {
310
- "type": "ActivityRelatedTo",
311
- "options": [
312
- {"key": "CONTACT", "value": "Contact"},
313
- {"key": "ACCOUNT", "value": "Account"},
314
- {"key": "DEAL", "value": "Deal"},
315
- ],
316
- "title": "ActivityRelatedTo",
256
+ "type": "string",
257
+ "options": [{"key": "ENTITY", "value": "Entity"}, {"key": "DEAL", "value": "Deal"}],
258
+ "title": "Related To Type",
317
259
  "description": "What type of record this activity is related to.",
318
- "enum": ["Contact", "Account", "Deal"],
319
- "x_enum_names": ["CONTACT", "ACCOUNT", "DEAL"],
260
+ "enum": ["Entity", "Deal"],
261
+ "x_enum_names": ["ENTITY", "DEAL"],
320
262
  },
321
263
  "related_to_id": {"type": "string", "title": "Related To ID"},
322
- "owner_email": {"type": "string", "title": "Owner Email"},
323
264
  "due_date": {"type": "datetime", "title": "Due Date", "format": "date-time"},
324
265
  "completed_at": {"type": "datetime", "title": "Completed At", "format": "date-time"},
325
266
  "is_completed": {"type": "boolean", "default": False, "title": "Is Completed"},
267
+ "assigned_to": {"type": "User", "title": "Assigned To"},
326
268
  },
327
- "custom_code": "import datetime as _dt\n\nfrom amsdal.contrib.auth.models.user import User\n\n\n@property\ndef display_name(self) -> str:\n \"\"\"Return display name for the activity.\"\"\"\n return f'{self.activity_type.value}: {self.subject}'\n\nasync def apre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = await self.aget_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n await super().apre_update()\n\ndef has_object_permission(self, user: 'User', action: str) -> bool:\n \"\"\"Check if user has permission to perform action on this activity.\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.owner_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 == 'Activity' and permission.action in ('*', action):\n return True\n return False\n\ndef pre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n super().pre_create()\n\ndef pre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = self.get_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n super().pre_update()",
269
+ "custom_code": "import datetime as _dt\n\nfrom amsdal.contrib.auth.models.user import User\n\n\n@property\ndef display_name(self) -> str:\n \"\"\"Return display name for the activity.\"\"\"\n return f'{self.activity_type.value}: {self.subject}'\n\nasync def apre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = await self.aget_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n await super().apre_update()\n\ndef has_object_permission(self, user: 'User', action: str) -> bool:\n \"\"\"Check if user has permission to perform action on this activity.\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 == 'Activity' and permission.action in ('*', action):\n return True\n return False\n\ndef pre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n super().pre_create()\n\ndef pre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = self.get_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n super().pre_update()",
328
270
  "storage_metadata": {
329
271
  "table_name": "Note",
330
272
  "db_fields": {},
331
273
  "primary_key": ["partition_key"],
332
- "indexed": [["related_to_id"], ["owner_email"], ["created_at"], ["due_date"]],
274
+ "indexed": [["related_to_id"], ["created_at"], ["due_date"]],
333
275
  "foreign_keys": {},
334
276
  },
335
277
  "description": "Simple note activity.",
@@ -341,7 +283,7 @@ class Migration(migrations.Migration):
341
283
  new_schema={
342
284
  "title": "Task",
343
285
  "type": "Activity",
344
- "required": ["subject", "related_to_type", "related_to_id", "owner_email"],
286
+ "required": ["subject"],
345
287
  "properties": {
346
288
  "created_at": {"type": "datetime", "title": "Created At", "format": "date-time"},
347
289
  "updated_at": {"type": "datetime", "title": "Updated At", "format": "date-time"},
@@ -349,22 +291,18 @@ class Migration(migrations.Migration):
349
291
  "subject": {"type": "string", "title": "Subject"},
350
292
  "description": {"type": "string", "title": "Description"},
351
293
  "related_to_type": {
352
- "type": "ActivityRelatedTo",
353
- "options": [
354
- {"key": "CONTACT", "value": "Contact"},
355
- {"key": "ACCOUNT", "value": "Account"},
356
- {"key": "DEAL", "value": "Deal"},
357
- ],
358
- "title": "ActivityRelatedTo",
294
+ "type": "string",
295
+ "options": [{"key": "ENTITY", "value": "Entity"}, {"key": "DEAL", "value": "Deal"}],
296
+ "title": "Related To Type",
359
297
  "description": "What type of record this activity is related to.",
360
- "enum": ["Contact", "Account", "Deal"],
361
- "x_enum_names": ["CONTACT", "ACCOUNT", "DEAL"],
298
+ "enum": ["Entity", "Deal"],
299
+ "x_enum_names": ["ENTITY", "DEAL"],
362
300
  },
363
301
  "related_to_id": {"type": "string", "title": "Related To ID"},
364
- "owner_email": {"type": "string", "title": "Owner Email"},
365
302
  "due_date": {"type": "datetime", "title": "Due Date", "format": "date-time"},
366
303
  "completed_at": {"type": "datetime", "title": "Completed At", "format": "date-time"},
367
304
  "is_completed": {"type": "boolean", "default": False, "title": "Is Completed"},
305
+ "assigned_to": {"type": "User", "title": "Assigned To"},
368
306
  "priority": {
369
307
  "type": "string",
370
308
  "default": "medium",
@@ -389,12 +327,12 @@ class Migration(migrations.Migration):
389
327
  "enum": ["not_started", "in_progress", "waiting", "completed"],
390
328
  },
391
329
  },
392
- "custom_code": "import datetime as _dt\n\nfrom amsdal.contrib.auth.models.user import User\n\n\n@property\ndef display_name(self) -> str:\n \"\"\"Return display name for the activity.\"\"\"\n return f'{self.activity_type.value}: {self.subject}'\n\nasync def apre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = await self.aget_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n await super().apre_update()\n\ndef has_object_permission(self, user: 'User', action: str) -> bool:\n \"\"\"Check if user has permission to perform action on this activity.\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.owner_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 == 'Activity' and permission.action in ('*', action):\n return True\n return False\n\ndef pre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n super().pre_create()\n\ndef pre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = self.get_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n super().pre_update()",
330
+ "custom_code": "import datetime as _dt\n\nfrom amsdal.contrib.auth.models.user import User\n\n\n@property\ndef display_name(self) -> str:\n \"\"\"Return display name for the activity.\"\"\"\n return f'{self.activity_type.value}: {self.subject}'\n\nasync def apre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n await super().apre_create()\n\nasync def apre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = await self.aget_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n await super().apre_update()\n\ndef has_object_permission(self, user: 'User', action: str) -> bool:\n \"\"\"Check if user has permission to perform action on this activity.\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 == 'Activity' and permission.action in ('*', action):\n return True\n return False\n\ndef pre_create(self) -> None:\n self.created_at = _dt.datetime.now(tz=_dt.UTC)\n super().pre_create()\n\ndef pre_update(self) -> None:\n self.updated_at = _dt.datetime.now(tz=_dt.UTC)\n if not self.created_at:\n _metadata = self.get_metadata()\n self.created_at = _dt.datetime.fromtimestamp(_metadata.created_at / 1000, tz=_dt.UTC)\n super().pre_update()",
393
331
  "storage_metadata": {
394
332
  "table_name": "Task",
395
333
  "db_fields": {},
396
334
  "primary_key": ["partition_key"],
397
- "indexed": [["related_to_id"], ["owner_email"], ["created_at"], ["due_date"]],
335
+ "indexed": [["related_to_id"], ["created_at"], ["due_date"]],
398
336
  "foreign_keys": {},
399
337
  },
400
338
  "description": "Task activity with priority and status.",
@@ -413,6 +351,7 @@ class Migration(migrations.Migration):
413
351
  "description": "Sales pipeline model.\n\nRepresents a sales pipeline with multiple stages.\nPipelines are system-wide and not owned by individual users.",
414
352
  },
415
353
  "name": {"type": "string", "title": "Stage Name"},
354
+ "description": {"type": "string", "title": "Description"},
416
355
  "order": {"type": "integer", "title": "Order"},
417
356
  "probability": {
418
357
  "type": "number",
@@ -421,8 +360,17 @@ class Migration(migrations.Migration):
421
360
  "maximum": 100,
422
361
  "minimum": 0,
423
362
  },
424
- "is_closed_won": {"type": "boolean", "default": False, "title": "Is Closed Won"},
425
- "is_closed_lost": {"type": "boolean", "default": False, "title": "Is Closed Lost"},
363
+ "status": {
364
+ "type": "string",
365
+ "default": "open",
366
+ "options": [
367
+ {"key": "open", "value": "open"},
368
+ {"key": "closed_won", "value": "closed_won"},
369
+ {"key": "closed_lost", "value": "closed_lost"},
370
+ ],
371
+ "title": "Status",
372
+ "enum": ["open", "closed_won", "closed_lost"],
373
+ },
426
374
  },
427
375
  "custom_code": '@property\ndef display_name(self) -> str:\n """Return display name for the stage."""\n if isinstance(self.pipeline, str):\n return f\'{self.pipeline} - {self.name}\'\n return f\'{self.pipeline.display_name} - {self.name}\'',
428
376
  "storage_metadata": {
@@ -440,48 +388,52 @@ class Migration(migrations.Migration):
440
388
  class_name="Deal",
441
389
  new_schema={
442
390
  "title": "Deal",
443
- "required": ["name", "stage", "owner_email"],
391
+ "required": ["name", "entity", "stage"],
444
392
  "properties": {
445
393
  "created_at": {"type": "datetime", "title": "Created At", "format": "date-time"},
446
394
  "updated_at": {"type": "datetime", "title": "Updated At", "format": "date-time"},
447
395
  "name": {"type": "string", "title": "Deal Name"},
448
396
  "amount": {"type": "number", "title": "Amount"},
449
397
  "currency": {"type": "string", "default": "USD", "title": "Currency"},
450
- "account": {
451
- "type": "Account",
452
- "title": "Account",
453
- "description": "Account (Company/Organization) model.\n\nRepresents a company or organization in the CRM system.\nOwned by individual users with permission controls.",
454
- },
455
- "contact": {
456
- "type": "Contact",
457
- "title": "Primary Contact",
458
- "description": "Contact (Person) model.\n\nRepresents a person in the CRM system, optionally linked to an Account.\nOwned by individual users with permission controls.",
398
+ "entity": {
399
+ "type": "Entity",
400
+ "title": "Entity",
401
+ "description": "Entity (Person/Organization/Trust) model.\n\nRepresents a company or organization in the CRM system.\nOwned by individual users with permission controls.",
459
402
  },
460
403
  "stage": {
461
404
  "type": "Stage",
462
405
  "title": "Stage",
463
406
  "description": "Pipeline stage model.\n\nRepresents a stage within a sales pipeline with win probability\nand closed status indicators.",
464
407
  },
465
- "owner_email": {"type": "string", "title": "Owner Email"},
466
408
  "expected_close_date": {"type": "datetime", "title": "Expected Close Date", "format": "date-time"},
467
409
  "closed_date": {"type": "datetime", "title": "Closed Date", "format": "date-time"},
468
- "is_closed": {"type": "boolean", "default": False, "title": "Is Closed"},
469
- "is_won": {"type": "boolean", "default": False, "title": "Is Won"},
410
+ "status": {
411
+ "type": "string",
412
+ "default": "open",
413
+ "options": [
414
+ {"key": "open", "value": "open"},
415
+ {"key": "closed_won", "value": "closed_won"},
416
+ {"key": "closed_lost", "value": "closed_lost"},
417
+ ],
418
+ "title": "Status",
419
+ "enum": ["open", "closed_won", "closed_lost"],
420
+ },
470
421
  "custom_fields": {"type": "anything", "title": "Custom Fields"},
422
+ "assigned_to": {"type": "User", "title": "Assigned To"},
471
423
  },
472
- "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 apost_update(self) -> None:\n """Async hook called after updating deal."""\n from amsdal_crm.services.workflow_service import WorkflowService\n WorkflowService.execute_rules(\'Deal\', \'update\', self)\n\nasync def apre_create(self) -> None:\n """Async hook called before creating deal."""\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = await CustomFieldService.avalidate_custom_fields(\'Deal\', self.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 if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = await CustomFieldService.avalidate_custom_fields(\'Deal\', self.custom_fields)\n stage = await self.stage\n self.is_closed = stage.is_closed_won or stage.is_closed_lost\n self.is_won = stage.is_closed_won\n if self.is_closed and (not self.closed_date):\n self.closed_date = _dt.datetime.now(_dt.UTC)\n await super().apre_update()\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.owner_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 """Hook called before creating deal."""\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = CustomFieldService.validate_custom_fields(\'Deal\', self.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 if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = CustomFieldService.validate_custom_fields(\'Deal\', self.custom_fields)\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 self.is_closed = stage.is_closed_won or stage.is_closed_lost\n self.is_won = stage.is_closed_won\n if self.is_closed and (not self.closed_date):\n self.closed_date = _dt.datetime.now(_dt.UTC)\n super().pre_update()',
424
+ "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 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 \"\"\"Async hook called before creating deal.\"\"\"\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = await CustomFieldService.avalidate_custom_fields('Deal', self.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 if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = await CustomFieldService.avalidate_custom_fields('Deal', self.custom_fields)\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 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 \"\"\"Hook called before creating deal.\"\"\"\n if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = CustomFieldService.validate_custom_fields('Deal', self.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 if self.custom_fields:\n from amsdal_crm.services.custom_field_service import CustomFieldService\n self.custom_fields = CustomFieldService.validate_custom_fields('Deal', self.custom_fields)\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()",
473
425
  "storage_metadata": {
474
426
  "table_name": "Deal",
475
427
  "db_fields": {
476
- "account": ["account_partition_key"],
477
- "contact": ["contact_partition_key"],
428
+ "entity": ["entity_partition_key"],
478
429
  "stage": ["stage_partition_key"],
430
+ "assigned_to": ["assigned_to_partition_key"],
479
431
  },
480
432
  "primary_key": ["partition_key"],
481
- "indexed": [["owner_email"], ["expected_close_date"], ["created_at"]],
433
+ "indexed": [["expected_close_date"], ["created_at"]],
482
434
  "foreign_keys": {
483
- "account": [{"account_partition_key": "string"}, "Account", ["partition_key"]],
484
- "contact": [{"contact_partition_key": "string"}, "Contact", ["partition_key"]],
435
+ "assigned_to": [{"assigned_to_partition_key": "string"}, "User", ["partition_key"]],
436
+ "entity": [{"entity_partition_key": "string"}, "Entity", ["partition_key"]],
485
437
  "stage": [{"stage_partition_key": "string"}, "Stage", ["partition_key"]],
486
438
  },
487
439
  },
@@ -498,13 +450,12 @@ class Migration(migrations.Migration):
498
450
  "related_to_type": {
499
451
  "type": "string",
500
452
  "options": [
501
- {"key": "Contact", "value": "Contact"},
502
- {"key": "Account", "value": "Account"},
453
+ {"key": "Entity", "value": "Entity"},
503
454
  {"key": "Deal", "value": "Deal"},
504
455
  {"key": "Activity", "value": "Activity"},
505
456
  ],
506
457
  "title": "Related To Type",
507
- "enum": ["Contact", "Account", "Deal", "Activity"],
458
+ "enum": ["Entity", "Deal", "Activity"],
508
459
  },
509
460
  "related_to_id": {"type": "string", "title": "Related To ID"},
510
461
  "uploaded_by": {"type": "string", "title": "Uploaded By (User Email)"},
@@ -533,12 +484,22 @@ class Migration(migrations.Migration):
533
484
  "entity_type": {
534
485
  "type": "string",
535
486
  "options": [
536
- {"key": "Contact", "value": "Contact"},
537
- {"key": "Account", "value": "Account"},
487
+ {"key": "Entity", "value": "Entity"},
488
+ {"key": "EntityRelationship", "value": "EntityRelationship"},
538
489
  {"key": "Deal", "value": "Deal"},
490
+ {"key": "EntityIdentifier", "value": "EntityIdentifier"},
491
+ {"key": "EntityContactPoint", "value": "EntityContactPoint"},
492
+ {"key": "EntityAddress", "value": "EntityAddress"},
539
493
  ],
540
494
  "title": "Entity Type",
541
- "enum": ["Contact", "Account", "Deal"],
495
+ "enum": [
496
+ "Entity",
497
+ "EntityRelationship",
498
+ "Deal",
499
+ "EntityIdentifier",
500
+ "EntityContactPoint",
501
+ "EntityAddress",
502
+ ],
542
503
  },
543
504
  "field_name": {"type": "string", "title": "Field Name"},
544
505
  "field_label": {"type": "string", "title": "Field Label"},
@@ -581,13 +542,12 @@ class Migration(migrations.Migration):
581
542
  "entity_type": {
582
543
  "type": "string",
583
544
  "options": [
584
- {"key": "Contact", "value": "Contact"},
585
- {"key": "Account", "value": "Account"},
545
+ {"key": "Entity", "value": "Entity"},
586
546
  {"key": "Deal", "value": "Deal"},
587
547
  {"key": "Activity", "value": "Activity"},
588
548
  ],
589
549
  "title": "Entity Type",
590
- "enum": ["Contact", "Account", "Deal", "Activity"],
550
+ "enum": ["Entity", "Deal", "Activity"],
591
551
  },
592
552
  "trigger_event": {
593
553
  "type": "string",