Skip to main content

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