Activities¶
Activities track interactions and work items related to entities and deals. The CRM supports five activity types: tasks, events, emails, notes, and calls.
Base Activity Fields¶
All activity types share these common fields:
| Field | Type | Default | Description |
|---|---|---|---|
activity_type |
ActivityType |
required | Discriminator: task, event, email, note, call |
subject |
str |
required | Activity subject/title |
description |
str \| None |
None |
Detailed description |
related_to_type |
ActivityRelatedTo \| None |
required | Related record type: Entity or Deal |
related_to_id |
str \| None |
required | ID of the related record |
assigned_to |
User \| None |
None |
Assigned user (FK) |
due_date |
datetime \| None |
None |
Due date |
completed_at |
datetime \| None |
None |
Completion timestamp |
is_completed |
bool |
False |
Whether the activity is completed |
created_at / updated_at |
datetime |
auto | Timestamps |
The ActivityManager automatically calls select_related('assigned_to') on all queries.
Polymorphic Linking¶
Activities link to entities or deals using a generic foreign key pattern:
related_to_type— anActivityRelatedToenum with valuesENTITY('Entity') andDEAL('Deal')related_to_id— the_object_idof the related record
Activity Types¶
Task¶
Tasks represent work items with priority and status tracking.
| Field | Type | Default | Description |
|---|---|---|---|
priority |
Literal['low', 'medium', 'high'] |
'medium' |
Task priority |
status |
Literal['not_started', 'in_progress', 'waiting', 'completed'] |
'not_started' |
Task status |
from amsdal_crm.models.activity import Task, ActivityType, ActivityRelatedTo
task = Task(
activity_type=ActivityType.TASK,
subject='Follow up with client',
related_to_type=ActivityRelatedTo.ENTITY,
related_to_id=entity._object_id,
priority='high',
status='not_started',
)
task.save(force_insert=True)
Event¶
Events represent meetings or calendar items.
| Field | Type | Default | Description |
|---|---|---|---|
start_time |
datetime |
required | Event start time |
end_time |
datetime |
required | Event end time |
location |
str \| None |
None |
Event location |
from datetime import datetime, timezone
from amsdal_crm.models.activity import Event, ActivityType, ActivityRelatedTo
event = Event(
activity_type=ActivityType.EVENT,
subject='Quarterly review meeting',
related_to_type=ActivityRelatedTo.DEAL,
related_to_id=deal._object_id,
start_time=datetime(2025, 3, 15, 14, 0, tzinfo=timezone.utc),
end_time=datetime(2025, 3, 15, 15, 0, tzinfo=timezone.utc),
location='Conference Room A',
)
event.save(force_insert=True)
EmailActivity¶
Email records linked to CRM entities or deals.
| Field | Type | Default | Description |
|---|---|---|---|
from_address |
str |
required | Sender email address |
to_addresses |
list[str] |
required | Recipient email addresses |
cc_addresses |
list[str] \| None |
None |
CC email addresses |
body |
str |
required | Email body |
is_outbound |
bool |
True |
True if sent, False if received |
Note¶
Notes are simple text records with no additional fields beyond the base activity fields.
from amsdal_crm.models.activity import Note, ActivityType, ActivityRelatedTo
note = Note(
activity_type=ActivityType.NOTE,
subject='Client prefers email communication',
description='Discussed communication preferences during onboarding call.',
related_to_type=ActivityRelatedTo.ENTITY,
related_to_id=entity._object_id,
)
note.save(force_insert=True)
Call¶
Phone call records.
| Field | Type | Default | Description |
|---|---|---|---|
phone_number |
str |
required | Phone number |
duration_seconds |
int \| None |
None |
Call duration in seconds |
call_outcome |
str \| None |
None |
Outcome description |
ActivityService¶
get_timeline¶
Returns a chronological activity feed for a record, sorted by created_at descending (newest first).
from amsdal_crm.services.activity_service import ActivityService
from amsdal_crm.models.activity import ActivityRelatedTo
# Get timeline for an entity
timeline = ActivityService.get_timeline(
related_to_type=ActivityRelatedTo.ENTITY,
related_to_id=entity._object_id,
limit=50,
)
# Async version
timeline = await ActivityService.aget_timeline(
related_to_type=ActivityRelatedTo.ENTITY,
related_to_id=entity._object_id,
limit=50,
)
The default limit is 100, configurable via the AMSDAL_CRM_DEFAULT_ACTIVITY_TIMELINE_LIMIT setting.
EmailService¶
log_email¶
Logs an email as an EmailActivity record, running inside a transaction.
from amsdal_crm.services.email_service import EmailService
from amsdal_crm.models.activity import ActivityRelatedTo
email = EmailService.log_email(
subject='Proposal for Q2',
body='Please find the attached proposal...',
from_address='sales@example.com',
to_addresses=['client@acme.com'],
cc_addresses=['manager@example.com'],
related_to_type=ActivityRelatedTo.DEAL,
related_to_id=deal._object_id,
is_outbound=True,
)
# Async version
email = await EmailService.alog_email(...)
Attachments¶
Attachments link files to entities, deals, or activities using the same polymorphic pattern.
| Field | Type | Default | Description |
|---|---|---|---|
file |
File |
required | AMSDAL File reference |
related_to_type |
Literal['Entity', 'Deal', 'Activity'] |
required | Related record type |
related_to_id |
str |
required | Related record ID |
uploaded_by |
str |
required | Uploader's email address |
uploaded_at |
datetime |
auto (UTC now) | Upload timestamp |
description |
str \| None |
None |
File description |
from amsdal_crm.models.attachment import Attachment
attachment = Attachment(
file=my_file,
related_to_type='Entity',
related_to_id=entity._object_id,
uploaded_by='user@example.com',
description='Signed contract',
)
attachment.save(force_insert=True)
Permissions¶
Activities use the same owner-based permission model as entities:
- The user in
assigned_tohas full access - Users with the
super_adminscope have full access