TorbatYar/backend/services/crm/app/schemas/collaboration.py
Mortezakoohjani 064d67f099 Ship CRM Core Platform (phases 6.0–6.3) with docs and prod deploy.
Add the Sales CRM service through collaboration, sync architecture/registry docs, expose crm.torbatyar.ir, and include a production deploy script.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 20:32:00 +03:30

502 lines
12 KiB
Python

"""Phase 6.3 collaboration DTOs."""
from __future__ import annotations
from datetime import datetime
from uuid import UUID
from pydantic import BaseModel, Field
from app.models.types import (
ActivityPriority,
ActivityRelatedType,
ActivityStatus,
ActivityType,
CallDirection,
CollaborationEntityType,
EmailDeliveryStatus,
RecurrenceRule,
TeamRoleCode,
TimelineEventType,
)
from app.schemas.common import ORMBase
class SalesActivityCreate(BaseModel):
subject: str = Field(max_length=255)
activity_type: ActivityType
related_type: ActivityRelatedType | None = None
related_id: UUID | None = None
lead_id: UUID | None = None
contact_id: UUID | None = None
organization_id: UUID | None = None
opportunity_id: UUID | None = None
due_at: datetime | None = None
owner_user_id: str | None = Field(default=None, max_length=100)
description: str | None = None
status: ActivityStatus = ActivityStatus.PLANNED
priority: ActivityPriority = ActivityPriority.MEDIUM
custom_type_label: str | None = Field(default=None, max_length=100)
class SalesActivityUpdate(BaseModel):
subject: str | None = Field(default=None, max_length=255)
activity_type: ActivityType | None = None
related_type: ActivityRelatedType | None = None
related_id: UUID | None = None
lead_id: UUID | None = None
contact_id: UUID | None = None
organization_id: UUID | None = None
opportunity_id: UUID | None = None
due_at: datetime | None = None
owner_user_id: str | None = Field(default=None, max_length=100)
description: str | None = None
status: ActivityStatus | None = None
priority: ActivityPriority | None = None
custom_type_label: str | None = Field(default=None, max_length=100)
class SalesActivityRead(ORMBase):
id: UUID
tenant_id: UUID
subject: str
activity_type: ActivityType
status: ActivityStatus
priority: ActivityPriority
related_type: ActivityRelatedType | None
related_id: UUID | None
lead_id: UUID | None
contact_id: UUID | None
organization_id: UUID | None
opportunity_id: UUID | None
due_at: datetime | None
completed_at: datetime | None
owner_user_id: str | None
description: str | None
custom_type_label: str | None
created_at: datetime
updated_at: datetime
class LookupCreate(BaseModel):
code: str = Field(max_length=50)
name: str = Field(max_length=150)
is_active: bool = True
class ActivityStatusLookupCreate(LookupCreate):
is_terminal: bool = False
class ActivityPriorityLookupCreate(LookupCreate):
rank: int = 0
class LookupRead(ORMBase):
id: UUID
tenant_id: UUID
code: str
name: str
is_active: bool
created_at: datetime
class ParticipantCreate(BaseModel):
activity_id: UUID
user_id: str | None = None
contact_id: UUID | None = None
role_label: str | None = None
is_required: bool = False
class ParticipantRead(ORMBase):
id: UUID
tenant_id: UUID
activity_id: UUID
user_id: str | None
contact_id: UUID | None
role_label: str | None
is_required: bool
created_at: datetime
class ReminderCreate(BaseModel):
activity_id: UUID
remind_at: datetime
channel: str = "in_app"
class ReminderRead(ORMBase):
id: UUID
tenant_id: UUID
activity_id: UUID
remind_at: datetime
channel: str
is_sent: bool
created_at: datetime
class TaskCreate(BaseModel):
title: str = Field(max_length=255)
description: str | None = None
owner_user_id: str | None = None
assignee_user_id: str | None = None
priority: str = "medium"
due_at: datetime | None = None
recurrence: RecurrenceRule = RecurrenceRule.NONE
opportunity_id: UUID | None = None
lead_id: UUID | None = None
contact_id: UUID | None = None
organization_id: UUID | None = None
activity_id: UUID | None = None
class TaskRead(ORMBase):
id: UUID
tenant_id: UUID
activity_id: UUID | None
title: str
description: str | None
owner_user_id: str | None
assignee_user_id: str | None
status: str
priority: str
due_at: datetime | None
completed_at: datetime | None
recurrence: RecurrenceRule
opportunity_id: UUID | None
lead_id: UUID | None
contact_id: UUID | None
organization_id: UUID | None
completion_percent: int
is_deleted: bool
created_at: datetime
updated_at: datetime
class ChecklistItemCreate(BaseModel):
task_id: UUID
title: str = Field(max_length=255)
position: int = 0
class ChecklistItemRead(ORMBase):
id: UUID
tenant_id: UUID
task_id: UUID
title: str
position: int
is_completed: bool
completed_at: datetime | None
created_at: datetime
class MeetingCreate(BaseModel):
subject: str = Field(max_length=255)
start_at: datetime
end_at: datetime
location: str | None = None
online_meeting_url: str | None = None
agenda: str | None = None
notes: str | None = None
owner_user_id: str | None = None
opportunity_id: UUID | None = None
lead_id: UUID | None = None
contact_id: UUID | None = None
organization_id: UUID | None = None
activity_id: UUID | None = None
class MeetingFinishRequest(BaseModel):
outcome: str | None = None
notes: str | None = None
class MeetingRead(ORMBase):
id: UUID
tenant_id: UUID
activity_id: UUID | None
subject: str
start_at: datetime
end_at: datetime
location: str | None
online_meeting_url: str | None
agenda: str | None
notes: str | None
outcome: str | None
owner_user_id: str | None
opportunity_id: UUID | None
lead_id: UUID | None
contact_id: UUID | None
organization_id: UUID | None
is_finished: bool
finished_at: datetime | None
created_at: datetime
updated_at: datetime
class MeetingAttendeeCreate(BaseModel):
meeting_id: UUID
user_id: str | None = None
contact_id: UUID | None = None
email: str | None = None
display_name: str | None = None
class MeetingAttendeeRead(ORMBase):
id: UUID
tenant_id: UUID
meeting_id: UUID
user_id: str | None
contact_id: UUID | None
email: str | None
display_name: str | None
response_status: str
created_at: datetime
class CallLogCreate(BaseModel):
direction: CallDirection
opportunity_id: UUID
subject: str | None = None
duration_seconds: int = 0
outcome_id: UUID | None = None
outcome_label: str | None = None
recording_ref: str | None = None
notes: str | None = None
owner_user_id: str | None = None
contact_id: UUID | None = None
lead_id: UUID | None = None
organization_id: UUID | None = None
called_at: datetime | None = None
activity_id: UUID | None = None
class CallLogRead(ORMBase):
id: UUID
tenant_id: UUID
activity_id: UUID | None
direction: CallDirection
subject: str | None
duration_seconds: int
outcome_id: UUID | None
outcome_label: str | None
recording_ref: str | None
notes: str | None
owner_user_id: str | None
opportunity_id: UUID | None
contact_id: UUID | None
lead_id: UUID | None
organization_id: UUID | None
called_at: datetime
created_at: datetime
class EmailThreadCreate(BaseModel):
subject: str = Field(max_length=255)
opportunity_id: UUID | None = None
contact_id: UUID | None = None
organization_id: UUID | None = None
lead_id: UUID | None = None
class EmailThreadRead(ORMBase):
id: UUID
tenant_id: UUID
subject: str
opportunity_id: UUID | None
contact_id: UUID | None
organization_id: UUID | None
lead_id: UUID | None
last_message_at: datetime | None
created_at: datetime
class EmailMessageCreate(BaseModel):
thread_id: UUID
subject: str = Field(max_length=255)
status: EmailDeliveryStatus = EmailDeliveryStatus.SENT
opportunity_id: UUID | None = None
notification_ref: str | None = None
opened: bool = False
clicked: bool = False
class EmailMessageRead(ORMBase):
id: UUID
tenant_id: UUID
thread_id: UUID
subject: str
status: EmailDeliveryStatus
opened: bool
clicked: bool
opportunity_id: UUID | None
notification_ref: str | None
sent_at: datetime | None
created_at: datetime
class TimelineEventRead(ORMBase):
id: UUID
tenant_id: UUID
event_type: TimelineEventType
entity_type: CollaborationEntityType
entity_id: UUID
related_opportunity_id: UUID | None
related_lead_id: UUID | None
related_contact_id: UUID | None
related_organization_id: UUID | None
actor_user_id: str | None
title: str
payload: dict | None
occurred_at: datetime
is_immutable: bool
created_at: datetime
class CommentCreate(BaseModel):
entity_type: CollaborationEntityType
entity_id: UUID
body: str
parent_comment_id: UUID | None = None
body_format: str = "richtext"
is_pinned: bool = False
mention_user_ids: list[str] = Field(default_factory=list)
class CommentUpdate(BaseModel):
body: str | None = None
is_pinned: bool | None = None
class CommentRead(ORMBase):
id: UUID
tenant_id: UUID
entity_type: CollaborationEntityType
entity_id: UUID
parent_comment_id: UUID | None
body: str
body_format: str
version: int
is_pinned: bool
is_deleted: bool
created_by: str | None
updated_by: str | None
created_at: datetime
updated_at: datetime
class MentionRead(ORMBase):
id: UUID
tenant_id: UUID
comment_id: UUID | None
entity_type: CollaborationEntityType
entity_id: UUID
mentioned_user_id: str
mentioned_by: str | None
is_read: bool
created_at: datetime
class BookmarkCreate(BaseModel):
entity_type: CollaborationEntityType
entity_id: UUID
label: str | None = None
class BookmarkRead(ORMBase):
id: UUID
tenant_id: UUID
user_id: str
entity_type: CollaborationEntityType
entity_id: UUID
label: str | None
created_at: datetime
class FavoriteCreate(BaseModel):
entity_type: CollaborationEntityType
entity_id: UUID
class FavoriteRead(ORMBase):
id: UUID
tenant_id: UUID
user_id: str
entity_type: CollaborationEntityType
entity_id: UUID
created_at: datetime
class TeamRoleCreate(BaseModel):
code: str = Field(max_length=50)
name: str = Field(max_length=150)
role_kind: TeamRoleCode = TeamRoleCode.CUSTOM
is_active: bool = True
class TeamRoleRead(ORMBase):
id: UUID
tenant_id: UUID
code: str
name: str
role_kind: TeamRoleCode
is_active: bool
created_at: datetime
class TeamMemberCreate(BaseModel):
user_id: str = Field(max_length=100)
team_key: str = Field(max_length=100)
display_name: str | None = None
role_id: UUID | None = None
role_code: TeamRoleCode = TeamRoleCode.SALESPERSON
is_active: bool = True
class TeamMemberRead(ORMBase):
id: UUID
tenant_id: UUID
user_id: str
display_name: str | None
team_key: str
role_id: UUID | None
role_code: TeamRoleCode
is_active: bool
created_at: datetime
class TeamAssignmentCreate(BaseModel):
team_key: str
member_id: UUID
entity_type: CollaborationEntityType
entity_id: UUID
class TeamAssignmentRead(ORMBase):
id: UUID
tenant_id: UUID
team_key: str
member_id: UUID
entity_type: CollaborationEntityType
entity_id: UUID
assigned_by: str | None
created_at: datetime
class NotificationPreferenceUpsert(BaseModel):
mentions_enabled: bool = True
task_reminders_enabled: bool = True
meeting_reminders_enabled: bool = True
timeline_digest_enabled: bool = False
class NotificationPreferenceRead(ORMBase):
id: UUID
tenant_id: UUID
user_id: str
mentions_enabled: bool
task_reminders_enabled: bool
meeting_reminders_enabled: bool
timeline_digest_enabled: bool
created_at: datetime