use serde::{Deserialize, Serialize};
use uuid::Uuid;
use chrono::{DateTime, Utc};
#[cfg(feature = "openapi")]
#[cfg(feature = "openapi")]
use utoipa::ToSchema;
use crate::domain::entity::Activity;
use crate::domain::entity::AuditMetadata;
use crate::domain::entity::ActivityDirection;
use crate::domain::entity::ActivityStatus;
use crate::domain::entity::ActivityType;
#[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct CreateActivityDto {
#[cfg_attr(feature = "openapi", schema(example = "example"))]
pub subject: String,
#[serde(alias = "activity_type")]
pub activity_type: ActivityType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub direction: Option<ActivityDirection>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "lead_id")]
pub lead_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "opportunity_id")]
pub opportunity_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "party_id")]
pub party_id: Option<Uuid>,
pub status: ActivityStatus,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "scheduled_at")]
pub scheduled_at: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "occurred_at")]
pub occurred_at: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "assignee_id")]
pub assignee_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub outcome: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct UpdateActivityDto {
#[cfg_attr(feature = "openapi", schema(example = "example"))]
pub subject: String,
#[serde(alias = "activity_type")]
pub activity_type: ActivityType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub direction: Option<ActivityDirection>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "lead_id")]
pub lead_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "opportunity_id")]
pub opportunity_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "party_id")]
pub party_id: Option<Uuid>,
pub status: ActivityStatus,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "scheduled_at")]
pub scheduled_at: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "occurred_at")]
pub occurred_at: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "assignee_id")]
pub assignee_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub outcome: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct PatchActivityDto {
#[cfg_attr(feature = "openapi", schema(example = "example"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub subject: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", alias = "activity_type")]
pub activity_type: Option<ActivityType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub direction: Option<ActivityDirection>,
#[serde(skip_serializing_if = "Option::is_none", alias = "lead_id")]
pub lead_id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none", alias = "opportunity_id")]
pub opportunity_id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none", alias = "party_id")]
pub party_id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<ActivityStatus>,
#[serde(skip_serializing_if = "Option::is_none", alias = "scheduled_at")]
pub scheduled_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none", alias = "occurred_at")]
pub occurred_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none", alias = "assignee_id")]
pub assignee_id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none")]
pub outcome: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
}
impl PatchActivityDto {
pub fn has_changes(&self) -> bool {
self.subject.is_some() || self.activity_type.is_some() || self.direction.is_some() || self.lead_id.is_some() || self.opportunity_id.is_some() || self.party_id.is_some() || self.status.is_some() || self.scheduled_at.is_some() || self.occurred_at.is_some() || self.assignee_id.is_some() || self.outcome.is_some() || self.notes.is_some()
}
}
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct ActivityResponseDto {
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
pub id: Uuid,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
pub subject: String,
pub activity_type: ActivityType,
pub direction: Option<ActivityDirection>,
pub lead_id: Option<Uuid>,
pub opportunity_id: Option<Uuid>,
pub party_id: Option<Uuid>,
pub status: ActivityStatus,
pub scheduled_at: Option<DateTime<Utc>>,
pub occurred_at: Option<DateTime<Utc>>,
pub assignee_id: Option<Uuid>,
pub outcome: Option<String>,
pub notes: Option<String>,
pub metadata: AuditMetadata,
}
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct ActivityListResponseDto {
pub items: Vec<ActivityResponseDto>,
pub total: u64,
pub page: u32,
pub per_page: u32,
pub total_pages: u32,
pub has_next: bool,
pub has_prev: bool,
}
impl ActivityListResponseDto {
pub fn new(items: Vec<ActivityResponseDto>, total: u64, page: u32, per_page: u32) -> Self {
let total_pages = if per_page > 0 {
((total as f64) / (per_page as f64)).ceil() as u32
} else {
0
};
Self {
items,
total,
page,
per_page,
total_pages,
has_next: page < total_pages,
has_prev: page > 1,
}
}
}
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct ActivitySummaryDto {
pub id: Uuid,
pub subject: String,
pub activity_type: ActivityType,
pub direction: Option<ActivityDirection>,
pub created_at: Option<DateTime<Utc>>,
}
impl From<Activity> for ActivityResponseDto {
fn from(entity: Activity) -> Self {
Self {
id: entity.id,
subject: entity.subject,
activity_type: entity.activity_type,
direction: entity.direction,
lead_id: entity.lead_id,
opportunity_id: entity.opportunity_id,
party_id: entity.party_id,
status: entity.status,
scheduled_at: entity.scheduled_at,
occurred_at: entity.occurred_at,
assignee_id: entity.assignee_id,
outcome: entity.outcome,
notes: entity.notes,
metadata: entity.metadata,
}
}
}
impl From<Activity> for ActivitySummaryDto {
fn from(entity: Activity) -> Self {
let created_at = backbone_core::PersistentEntity::created_at(&entity);
Self {
id: entity.id,
subject: entity.subject,
activity_type: entity.activity_type,
direction: entity.direction,
created_at,
}
}
}
impl From<CreateActivityDto> for Activity {
fn from(dto: CreateActivityDto) -> Self {
Self {
id: Uuid::new_v4(),
subject: dto.subject,
activity_type: dto.activity_type,
direction: dto.direction,
lead_id: dto.lead_id,
opportunity_id: dto.opportunity_id,
party_id: dto.party_id,
status: dto.status,
scheduled_at: dto.scheduled_at,
occurred_at: dto.occurred_at,
assignee_id: dto.assignee_id,
outcome: dto.outcome,
notes: dto.notes,
metadata: AuditMetadata::default(),
}
}
}
impl From<&Activity> for ActivityResponseDto {
fn from(entity: &Activity) -> Self {
Self {
id: entity.id.clone(),
subject: entity.subject.clone(),
activity_type: entity.activity_type.clone(),
direction: entity.direction.clone(),
lead_id: entity.lead_id.clone(),
opportunity_id: entity.opportunity_id.clone(),
party_id: entity.party_id.clone(),
status: entity.status.clone(),
scheduled_at: entity.scheduled_at.clone(),
occurred_at: entity.occurred_at.clone(),
assignee_id: entity.assignee_id.clone(),
outcome: entity.outcome.clone(),
notes: entity.notes.clone(),
metadata: entity.metadata.clone(),
}
}
}
impl backbone_core::FromCreateDto<CreateActivityDto> for Activity {
fn from_create_dto(dto: CreateActivityDto) -> backbone_core::ServiceResult<Self> {
Ok(Activity::from(dto))
}
}
impl backbone_core::ApplyUpdateDto<UpdateActivityDto> for Activity {
fn apply_update(mut self, dto: UpdateActivityDto) -> backbone_core::ServiceResult<Self> {
self.subject = dto.subject;
self.activity_type = dto.activity_type;
self.direction = dto.direction;
self.lead_id = dto.lead_id;
self.opportunity_id = dto.opportunity_id;
self.party_id = dto.party_id;
self.status = dto.status;
self.scheduled_at = dto.scheduled_at;
self.occurred_at = dto.occurred_at;
self.assignee_id = dto.assignee_id;
self.outcome = dto.outcome;
self.notes = dto.notes;
Ok(self)
}
}