backbone-activity 0.8.0

Sales activity / touchpoints + rep tasks — the substrate lead/deal sit on
Documentation
//! Activity DTOs (Data Transfer Objects)
//!
//! Generated by metaphor-schema. Do not edit manually.
//!
//! DTOs provide a clean separation between domain entities and API
//! representations, with OpenAPI documentation support.

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;

// =============================================================================
// Create DTO
// =============================================================================

/// Request DTO for creating a new Activity
///
/// Used in POST requests to create new entities.
/// Excludes auto-generated fields (id, created_at, updated_at, deleted_at).
#[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>,
}

// =============================================================================
// Update DTO
// =============================================================================

/// Request DTO for full update of a Activity
///
/// Used in PUT requests for full entity replacement.
/// All fields are required (except auto-generated ones).
#[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>,
}

// =============================================================================
// Patch DTO
// =============================================================================

/// Request DTO for partial update of a Activity
///
/// Used in PATCH requests for partial updates.
/// All fields are optional - only provided fields will be updated.
#[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 {
    /// Check if any field is set
    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()
    }
}

// =============================================================================
// Response DTO
// =============================================================================

/// Response DTO for Activity entity
///
/// Used in API responses for single entity.
/// Includes all fields including metadata.
#[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,
}

// =============================================================================
// List Response DTO
// =============================================================================

/// Paginated list response for Activity entities
///
/// Used in API responses for list endpoints.
/// Includes pagination metadata.
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct ActivityListResponseDto {
    /// List of items
    pub items: Vec<ActivityResponseDto>,
    /// Total number of items (across all pages)
    pub total: u64,
    /// Current page number (1-indexed)
    pub page: u32,
    /// Number of items per page
    pub per_page: u32,
    /// Total number of pages
    pub total_pages: u32,
    /// Whether there is a next page
    pub has_next: bool,
    /// Whether there is a previous page
    pub has_prev: bool,
}

impl ActivityListResponseDto {
    /// Create a new list response from items and pagination info
    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,
        }
    }
}

/// Summary DTO for list views (compact version)
#[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>>,
}

// =============================================================================
// Conversions
// =============================================================================

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)
    }
}

// =============================================================================
// Custom DTOs
// =============================================================================

// Add custom DTOs specific to Activity here.
// This section will be preserved during regeneration.
// >>> END CUSTOM DTOs