koprogo_api/application/dto/
shared_object_dto.rs

1use crate::domain::entities::{ObjectCondition, SharedObject, SharedObjectCategory};
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6/// DTO for creating a new shared object
7#[derive(Debug, Serialize, Deserialize)]
8pub struct CreateSharedObjectDto {
9    pub building_id: Uuid,
10    pub object_category: SharedObjectCategory,
11    pub object_name: String,
12    pub description: String,
13    pub condition: ObjectCondition,
14    pub is_available: bool,
15    pub rental_credits_per_day: Option<i32>, // 0-20 (SEL integration)
16    pub deposit_credits: Option<i32>,        // 0-100
17    pub borrowing_duration_days: Option<i32>, // 1-90
18    pub photos: Option<Vec<String>>,
19    pub location_details: Option<String>,
20    pub usage_instructions: Option<String>,
21}
22
23/// DTO for updating a shared object
24#[derive(Debug, Serialize, Deserialize)]
25pub struct UpdateSharedObjectDto {
26    pub object_name: Option<String>,
27    pub description: Option<String>,
28    pub condition: Option<ObjectCondition>,
29    pub is_available: Option<bool>,
30    pub rental_credits_per_day: Option<Option<i32>>,
31    pub deposit_credits: Option<Option<i32>>,
32    pub borrowing_duration_days: Option<Option<i32>>,
33    pub photos: Option<Option<Vec<String>>>,
34    pub location_details: Option<Option<String>>,
35    pub usage_instructions: Option<Option<String>>,
36}
37
38/// DTO for borrowing an object
39#[derive(Debug, Serialize, Deserialize)]
40pub struct BorrowObjectDto {
41    pub duration_days: Option<i32>, // Override default duration
42}
43
44/// Complete shared object response with owner/borrower information
45#[derive(Debug, Serialize, Clone)]
46pub struct SharedObjectResponseDto {
47    pub id: Uuid,
48    pub owner_id: Uuid,
49    pub owner_name: String, // Enriched from Owner
50    pub building_id: Uuid,
51    pub object_category: SharedObjectCategory,
52    pub object_name: String,
53    pub description: String,
54    pub condition: ObjectCondition,
55    pub is_available: bool,
56    pub rental_credits_per_day: Option<i32>,
57    pub deposit_credits: Option<i32>,
58    pub borrowing_duration_days: Option<i32>,
59    pub current_borrower_id: Option<Uuid>,
60    pub current_borrower_name: Option<String>, // Enriched from Owner
61    pub borrowed_at: Option<DateTime<Utc>>,
62    pub due_back_at: Option<DateTime<Utc>>,
63    pub photos: Option<Vec<String>>,
64    pub location_details: Option<String>,
65    pub usage_instructions: Option<String>,
66    pub created_at: DateTime<Utc>,
67    pub updated_at: DateTime<Utc>,
68    // Computed fields
69    pub is_free: bool,
70    pub is_borrowed: bool,
71    pub is_overdue: bool,
72    pub days_overdue: i32,
73}
74
75impl SharedObjectResponseDto {
76    /// Create from SharedObject with owner/borrower name enrichment
77    pub fn from_shared_object(
78        object: SharedObject,
79        owner_name: String,
80        borrower_name: Option<String>,
81    ) -> Self {
82        let is_free = object.is_free();
83        let is_borrowed = object.is_borrowed();
84        let is_overdue = object.is_overdue();
85        let days_overdue = object.days_overdue();
86
87        Self {
88            id: object.id,
89            owner_id: object.owner_id,
90            owner_name,
91            building_id: object.building_id,
92            object_category: object.object_category,
93            object_name: object.object_name,
94            description: object.description,
95            condition: object.condition,
96            is_available: object.is_available,
97            rental_credits_per_day: object.rental_credits_per_day,
98            deposit_credits: object.deposit_credits,
99            borrowing_duration_days: object.borrowing_duration_days,
100            current_borrower_id: object.current_borrower_id,
101            current_borrower_name: borrower_name,
102            borrowed_at: object.borrowed_at,
103            due_back_at: object.due_back_at,
104            photos: object.photos,
105            location_details: object.location_details,
106            usage_instructions: object.usage_instructions,
107            created_at: object.created_at,
108            updated_at: object.updated_at,
109            is_free,
110            is_borrowed,
111            is_overdue,
112            days_overdue,
113        }
114    }
115}
116
117/// Summary shared object view for lists
118#[derive(Debug, Serialize, Clone)]
119pub struct SharedObjectSummaryDto {
120    pub id: Uuid,
121    pub owner_id: Uuid,
122    pub owner_name: String, // Enriched from Owner
123    pub building_id: Uuid,
124    pub object_category: SharedObjectCategory,
125    pub object_name: String,
126    pub condition: ObjectCondition,
127    pub is_available: bool,
128    pub rental_credits_per_day: Option<i32>,
129    pub deposit_credits: Option<i32>,
130    pub current_borrower_id: Option<Uuid>,
131    pub due_back_at: Option<DateTime<Utc>>,
132    pub is_free: bool,
133    pub is_borrowed: bool,
134    pub is_overdue: bool,
135}
136
137impl SharedObjectSummaryDto {
138    /// Create from SharedObject with owner name enrichment
139    pub fn from_shared_object(object: SharedObject, owner_name: String) -> Self {
140        let is_free = object.is_free();
141        let is_borrowed = object.is_borrowed();
142        let is_overdue = object.is_overdue();
143
144        Self {
145            id: object.id,
146            owner_id: object.owner_id,
147            owner_name,
148            building_id: object.building_id,
149            object_category: object.object_category,
150            object_name: object.object_name,
151            condition: object.condition,
152            is_available: object.is_available,
153            rental_credits_per_day: object.rental_credits_per_day,
154            deposit_credits: object.deposit_credits,
155            current_borrower_id: object.current_borrower_id,
156            due_back_at: object.due_back_at,
157            is_free,
158            is_borrowed,
159            is_overdue,
160        }
161    }
162}
163
164/// Statistics for building shared objects
165#[derive(Debug, Serialize)]
166pub struct SharedObjectStatisticsDto {
167    pub total_objects: i64,
168    pub available_objects: i64,
169    pub borrowed_objects: i64,
170    pub overdue_objects: i64,
171    pub free_objects: i64,
172    pub paid_objects: i64,
173    pub objects_by_category: Vec<CategoryObjectCount>,
174}
175
176/// Category count for statistics
177#[derive(Debug, Serialize)]
178pub struct CategoryObjectCount {
179    pub category: SharedObjectCategory,
180    pub count: i64,
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn test_shared_object_response_dto_from_shared_object() {
189        let object = SharedObject::new(
190            Uuid::new_v4(),
191            Uuid::new_v4(),
192            SharedObjectCategory::Tools,
193            "Power Drill".to_string(),
194            "18V cordless drill".to_string(),
195            ObjectCondition::Good,
196            true,
197            Some(2),
198            Some(10),
199            Some(7),
200            None,
201            None,
202            None,
203        )
204        .unwrap();
205
206        let dto = SharedObjectResponseDto::from_shared_object(
207            object.clone(),
208            "John Doe".to_string(),
209            None,
210        );
211
212        assert_eq!(dto.owner_name, "John Doe");
213        assert_eq!(dto.object_name, "Power Drill");
214        assert!(!dto.is_free);
215        assert!(!dto.is_borrowed);
216        assert!(!dto.is_overdue);
217    }
218
219    #[test]
220    fn test_shared_object_summary_dto_from_shared_object() {
221        let object = SharedObject::new(
222            Uuid::new_v4(),
223            Uuid::new_v4(),
224            SharedObjectCategory::Books,
225            "Book Title".to_string(),
226            "Description".to_string(),
227            ObjectCondition::Excellent,
228            true,
229            None, // Free
230            None,
231            None,
232            None,
233            None,
234            None,
235        )
236        .unwrap();
237
238        let dto =
239            SharedObjectSummaryDto::from_shared_object(object.clone(), "Jane Smith".to_string());
240
241        assert_eq!(dto.owner_name, "Jane Smith");
242        assert_eq!(dto.object_name, "Book Title");
243        assert!(dto.is_free);
244        assert!(!dto.is_borrowed);
245    }
246}