Skip to main content

backbone_activity/presentation/dto/
activity_dto.rs

1//! Activity DTOs (Data Transfer Objects)
2//!
3//! Generated by metaphor-schema. Do not edit manually.
4//!
5//! DTOs provide a clean separation between domain entities and API
6//! representations, with OpenAPI documentation support.
7
8use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10use chrono::{DateTime, Utc};
11
12#[cfg(feature = "openapi")]
13#[cfg(feature = "openapi")]
14use utoipa::ToSchema;
15
16use crate::domain::entity::Activity;
17use crate::domain::entity::AuditMetadata;
18use crate::domain::entity::ActivityDirection;
19use crate::domain::entity::ActivityStatus;
20use crate::domain::entity::ActivityType;
21
22// =============================================================================
23// Create DTO
24// =============================================================================
25
26/// Request DTO for creating a new Activity
27///
28/// Used in POST requests to create new entities.
29/// Excludes auto-generated fields (id, created_at, updated_at, deleted_at).
30#[derive(Debug, Clone, Deserialize)]
31#[cfg_attr(feature = "openapi", derive(ToSchema))]
32#[serde(rename_all = "camelCase")]
33pub struct CreateActivityDto {
34    #[cfg_attr(feature = "openapi", schema(example = "example"))]
35    pub subject: String,
36    #[serde(alias = "activity_type")]
37    pub activity_type: ActivityType,
38    #[serde(default, skip_serializing_if = "Option::is_none")]
39    pub direction: Option<ActivityDirection>,
40    #[serde(default, skip_serializing_if = "Option::is_none", alias = "lead_id")]
41    pub lead_id: Option<Uuid>,
42    #[serde(default, skip_serializing_if = "Option::is_none", alias = "opportunity_id")]
43    pub opportunity_id: Option<Uuid>,
44    #[serde(default, skip_serializing_if = "Option::is_none", alias = "party_id")]
45    pub party_id: Option<Uuid>,
46    pub status: ActivityStatus,
47    #[serde(default, skip_serializing_if = "Option::is_none", alias = "scheduled_at")]
48    pub scheduled_at: Option<DateTime<Utc>>,
49    #[serde(default, skip_serializing_if = "Option::is_none", alias = "occurred_at")]
50    pub occurred_at: Option<DateTime<Utc>>,
51    #[serde(default, skip_serializing_if = "Option::is_none", alias = "assignee_id")]
52    pub assignee_id: Option<Uuid>,
53    #[serde(default, skip_serializing_if = "Option::is_none")]
54    pub outcome: Option<String>,
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub notes: Option<String>,
57}
58
59// =============================================================================
60// Update DTO
61// =============================================================================
62
63/// Request DTO for full update of a Activity
64///
65/// Used in PUT requests for full entity replacement.
66/// All fields are required (except auto-generated ones).
67#[derive(Debug, Clone, Deserialize)]
68#[cfg_attr(feature = "openapi", derive(ToSchema))]
69#[serde(rename_all = "camelCase")]
70pub struct UpdateActivityDto {
71    #[cfg_attr(feature = "openapi", schema(example = "example"))]
72    pub subject: String,
73    #[serde(alias = "activity_type")]
74    pub activity_type: ActivityType,
75    #[serde(default, skip_serializing_if = "Option::is_none")]
76    pub direction: Option<ActivityDirection>,
77    #[serde(default, skip_serializing_if = "Option::is_none", alias = "lead_id")]
78    pub lead_id: Option<Uuid>,
79    #[serde(default, skip_serializing_if = "Option::is_none", alias = "opportunity_id")]
80    pub opportunity_id: Option<Uuid>,
81    #[serde(default, skip_serializing_if = "Option::is_none", alias = "party_id")]
82    pub party_id: Option<Uuid>,
83    pub status: ActivityStatus,
84    #[serde(default, skip_serializing_if = "Option::is_none", alias = "scheduled_at")]
85    pub scheduled_at: Option<DateTime<Utc>>,
86    #[serde(default, skip_serializing_if = "Option::is_none", alias = "occurred_at")]
87    pub occurred_at: Option<DateTime<Utc>>,
88    #[serde(default, skip_serializing_if = "Option::is_none", alias = "assignee_id")]
89    pub assignee_id: Option<Uuid>,
90    #[serde(default, skip_serializing_if = "Option::is_none")]
91    pub outcome: Option<String>,
92    #[serde(default, skip_serializing_if = "Option::is_none")]
93    pub notes: Option<String>,
94}
95
96// =============================================================================
97// Patch DTO
98// =============================================================================
99
100/// Request DTO for partial update of a Activity
101///
102/// Used in PATCH requests for partial updates.
103/// All fields are optional - only provided fields will be updated.
104#[derive(Debug, Clone, Default, Deserialize)]
105#[cfg_attr(feature = "openapi", derive(ToSchema))]
106#[serde(rename_all = "camelCase")]
107pub struct PatchActivityDto {
108    #[cfg_attr(feature = "openapi", schema(example = "example"))]
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub subject: Option<String>,
111    #[serde(skip_serializing_if = "Option::is_none", alias = "activity_type")]
112    pub activity_type: Option<ActivityType>,
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub direction: Option<ActivityDirection>,
115    #[serde(skip_serializing_if = "Option::is_none", alias = "lead_id")]
116    pub lead_id: Option<Uuid>,
117    #[serde(skip_serializing_if = "Option::is_none", alias = "opportunity_id")]
118    pub opportunity_id: Option<Uuid>,
119    #[serde(skip_serializing_if = "Option::is_none", alias = "party_id")]
120    pub party_id: Option<Uuid>,
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub status: Option<ActivityStatus>,
123    #[serde(skip_serializing_if = "Option::is_none", alias = "scheduled_at")]
124    pub scheduled_at: Option<DateTime<Utc>>,
125    #[serde(skip_serializing_if = "Option::is_none", alias = "occurred_at")]
126    pub occurred_at: Option<DateTime<Utc>>,
127    #[serde(skip_serializing_if = "Option::is_none", alias = "assignee_id")]
128    pub assignee_id: Option<Uuid>,
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub outcome: Option<String>,
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub notes: Option<String>,
133}
134
135impl PatchActivityDto {
136    /// Check if any field is set
137    pub fn has_changes(&self) -> bool {
138        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()
139    }
140}
141
142// =============================================================================
143// Response DTO
144// =============================================================================
145
146/// Response DTO for Activity entity
147///
148/// Used in API responses for single entity.
149/// Includes all fields including metadata.
150#[derive(Debug, Clone, Serialize)]
151#[cfg_attr(feature = "openapi", derive(ToSchema))]
152#[serde(rename_all = "camelCase")]
153pub struct ActivityResponseDto {
154    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
155    pub id: Uuid,
156    #[cfg_attr(feature = "openapi", schema(example = "example"))]
157    pub subject: String,
158    pub activity_type: ActivityType,
159    pub direction: Option<ActivityDirection>,
160    pub lead_id: Option<Uuid>,
161    pub opportunity_id: Option<Uuid>,
162    pub party_id: Option<Uuid>,
163    pub status: ActivityStatus,
164    pub scheduled_at: Option<DateTime<Utc>>,
165    pub occurred_at: Option<DateTime<Utc>>,
166    pub assignee_id: Option<Uuid>,
167    pub outcome: Option<String>,
168    pub notes: Option<String>,
169    pub metadata: AuditMetadata,
170}
171
172// =============================================================================
173// List Response DTO
174// =============================================================================
175
176/// Paginated list response for Activity entities
177///
178/// Used in API responses for list endpoints.
179/// Includes pagination metadata.
180#[derive(Debug, Clone, Serialize)]
181#[cfg_attr(feature = "openapi", derive(ToSchema))]
182#[serde(rename_all = "camelCase")]
183pub struct ActivityListResponseDto {
184    /// List of items
185    pub items: Vec<ActivityResponseDto>,
186    /// Total number of items (across all pages)
187    pub total: u64,
188    /// Current page number (1-indexed)
189    pub page: u32,
190    /// Number of items per page
191    pub per_page: u32,
192    /// Total number of pages
193    pub total_pages: u32,
194    /// Whether there is a next page
195    pub has_next: bool,
196    /// Whether there is a previous page
197    pub has_prev: bool,
198}
199
200impl ActivityListResponseDto {
201    /// Create a new list response from items and pagination info
202    pub fn new(items: Vec<ActivityResponseDto>, total: u64, page: u32, per_page: u32) -> Self {
203        let total_pages = if per_page > 0 {
204            ((total as f64) / (per_page as f64)).ceil() as u32
205        } else {
206            0
207        };
208        Self {
209            items,
210            total,
211            page,
212            per_page,
213            total_pages,
214            has_next: page < total_pages,
215            has_prev: page > 1,
216        }
217    }
218}
219
220/// Summary DTO for list views (compact version)
221#[derive(Debug, Clone, Serialize)]
222#[cfg_attr(feature = "openapi", derive(ToSchema))]
223#[serde(rename_all = "camelCase")]
224pub struct ActivitySummaryDto {
225    pub id: Uuid,
226    pub subject: String,
227    pub activity_type: ActivityType,
228    pub direction: Option<ActivityDirection>,
229    pub created_at: Option<DateTime<Utc>>,
230}
231
232// =============================================================================
233// Conversions
234// =============================================================================
235
236impl From<Activity> for ActivityResponseDto {
237    fn from(entity: Activity) -> Self {
238        Self {
239            id: entity.id,
240            subject: entity.subject,
241            activity_type: entity.activity_type,
242            direction: entity.direction,
243            lead_id: entity.lead_id,
244            opportunity_id: entity.opportunity_id,
245            party_id: entity.party_id,
246            status: entity.status,
247            scheduled_at: entity.scheduled_at,
248            occurred_at: entity.occurred_at,
249            assignee_id: entity.assignee_id,
250            outcome: entity.outcome,
251            notes: entity.notes,
252            metadata: entity.metadata,
253        }
254    }
255}
256
257impl From<Activity> for ActivitySummaryDto {
258    fn from(entity: Activity) -> Self {
259        let created_at = backbone_core::PersistentEntity::created_at(&entity);
260        Self {
261            id: entity.id,
262            subject: entity.subject,
263            activity_type: entity.activity_type,
264            direction: entity.direction,
265            created_at,
266        }
267    }
268}
269
270impl From<CreateActivityDto> for Activity {
271    fn from(dto: CreateActivityDto) -> Self {
272        Self {
273            id: Uuid::new_v4(),
274            subject: dto.subject,
275            activity_type: dto.activity_type,
276            direction: dto.direction,
277            lead_id: dto.lead_id,
278            opportunity_id: dto.opportunity_id,
279            party_id: dto.party_id,
280            status: dto.status,
281            scheduled_at: dto.scheduled_at,
282            occurred_at: dto.occurred_at,
283            assignee_id: dto.assignee_id,
284            outcome: dto.outcome,
285            notes: dto.notes,
286            metadata: AuditMetadata::default(),
287        }
288    }
289}
290
291impl From<&Activity> for ActivityResponseDto {
292    fn from(entity: &Activity) -> Self {
293        Self {
294            id: entity.id.clone(),
295            subject: entity.subject.clone(),
296            activity_type: entity.activity_type.clone(),
297            direction: entity.direction.clone(),
298            lead_id: entity.lead_id.clone(),
299            opportunity_id: entity.opportunity_id.clone(),
300            party_id: entity.party_id.clone(),
301            status: entity.status.clone(),
302            scheduled_at: entity.scheduled_at.clone(),
303            occurred_at: entity.occurred_at.clone(),
304            assignee_id: entity.assignee_id.clone(),
305            outcome: entity.outcome.clone(),
306            notes: entity.notes.clone(),
307            metadata: entity.metadata.clone(),
308        }
309    }
310}
311
312impl backbone_core::FromCreateDto<CreateActivityDto> for Activity {
313    fn from_create_dto(dto: CreateActivityDto) -> backbone_core::ServiceResult<Self> {
314        Ok(Activity::from(dto))
315    }
316}
317
318impl backbone_core::ApplyUpdateDto<UpdateActivityDto> for Activity {
319    fn apply_update(mut self, dto: UpdateActivityDto) -> backbone_core::ServiceResult<Self> {
320        self.subject = dto.subject;
321        self.activity_type = dto.activity_type;
322        self.direction = dto.direction;
323        self.lead_id = dto.lead_id;
324        self.opportunity_id = dto.opportunity_id;
325        self.party_id = dto.party_id;
326        self.status = dto.status;
327        self.scheduled_at = dto.scheduled_at;
328        self.occurred_at = dto.occurred_at;
329        self.assignee_id = dto.assignee_id;
330        self.outcome = dto.outcome;
331        self.notes = dto.notes;
332        Ok(self)
333    }
334}
335
336// =============================================================================
337// Custom DTOs
338// =============================================================================
339
340// Add custom DTOs specific to Activity here.
341// This section will be preserved during regeneration.
342// >>> END CUSTOM DTOs
343