koprogo_api/application/dto/
quote_dto.rs1use crate::domain::entities::{Quote, QuoteScore};
2use rust_decimal::Decimal;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct CreateQuoteDto {
8 pub building_id: String,
9 pub contractor_id: String,
10 pub project_title: String,
11 pub project_description: String,
12 pub amount_excl_vat: Decimal,
13 pub vat_rate: Decimal,
14 pub validity_date: String, pub estimated_start_date: Option<String>,
16 pub estimated_duration_days: i32,
17 pub warranty_years: i32,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct UpdateQuoteDto {
23 pub amount_excl_vat: Option<Decimal>,
24 pub vat_rate: Option<Decimal>,
25 pub estimated_start_date: Option<String>,
26 pub estimated_duration_days: Option<i32>,
27 pub warranty_years: Option<i32>,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct QuoteDecisionDto {
33 pub decision_notes: Option<String>,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct QuoteComparisonRequestDto {
39 pub quote_ids: Vec<String>, }
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct QuoteResponseDto {
45 pub id: String,
46 pub building_id: String,
47 pub contractor_id: String,
48 pub project_title: String,
49 pub project_description: String,
50
51 pub amount_excl_vat: String, pub vat_rate: String, pub amount_incl_vat: String, pub validity_date: String,
56 pub estimated_start_date: Option<String>,
57 pub estimated_duration_days: i32,
58
59 pub warranty_years: i32,
61 pub contractor_rating: Option<i32>,
62
63 pub status: String,
65 pub is_expired: bool,
66
67 pub requested_at: String,
69 pub submitted_at: Option<String>,
70 pub reviewed_at: Option<String>,
71 pub decision_at: Option<String>,
72 pub decision_by: Option<String>,
73 pub decision_notes: Option<String>,
74
75 pub created_at: String,
77 pub updated_at: String,
78}
79
80impl From<Quote> for QuoteResponseDto {
81 fn from(quote: Quote) -> Self {
82 Self {
83 id: quote.id.to_string(),
84 building_id: quote.building_id.to_string(),
85 contractor_id: quote.contractor_id.to_string(),
86 project_title: quote.project_title.clone(),
87 project_description: quote.project_description.clone(),
88 amount_excl_vat: format!("{:.2}", quote.amount_excl_vat),
89 vat_rate: format!("{:.2}", quote.vat_rate),
90 amount_incl_vat: format!("{:.2}", quote.amount_incl_vat),
91 validity_date: quote.validity_date.to_rfc3339(),
92 estimated_start_date: quote.estimated_start_date.map(|d| d.to_rfc3339()),
93 estimated_duration_days: quote.estimated_duration_days,
94 warranty_years: quote.warranty_years,
95 contractor_rating: quote.contractor_rating,
96 status: quote.status.to_sql().to_string(),
97 is_expired: quote.is_expired(),
98 requested_at: quote.requested_at.to_rfc3339(),
99 submitted_at: quote.submitted_at.map(|d| d.to_rfc3339()),
100 reviewed_at: quote.reviewed_at.map(|d| d.to_rfc3339()),
101 decision_at: quote.decision_at.map(|d| d.to_rfc3339()),
102 decision_by: quote.decision_by.map(|u| u.to_string()),
103 decision_notes: quote.decision_notes,
104 created_at: quote.created_at.to_rfc3339(),
105 updated_at: quote.updated_at.to_rfc3339(),
106 }
107 }
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct QuoteScoreResponseDto {
113 pub quote_id: String,
114 pub total_score: f32,
115 pub price_score: f32,
116 pub delay_score: f32,
117 pub warranty_score: f32,
118 pub reputation_score: f32,
119}
120
121impl From<QuoteScore> for QuoteScoreResponseDto {
122 fn from(score: QuoteScore) -> Self {
123 Self {
124 quote_id: score.quote_id.to_string(),
125 total_score: score.total_score,
126 price_score: score.price_score,
127 delay_score: score.delay_score,
128 warranty_score: score.warranty_score,
129 reputation_score: score.reputation_score,
130 }
131 }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct QuoteComparisonItemDto {
137 pub quote: QuoteResponseDto,
138 pub score: Option<QuoteScoreResponseDto>,
139 pub rank: usize, }
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct QuoteComparisonResponseDto {
145 pub project_title: String,
146 pub building_id: String,
147 pub total_quotes: usize,
148 pub comparison_items: Vec<QuoteComparisonItemDto>,
149
150 pub min_price: String, pub max_price: String,
153 pub avg_price: String,
154 pub min_duration_days: i32,
155 pub max_duration_days: i32,
156 pub avg_duration_days: f32,
157
158 pub recommended_quote_id: Option<String>,
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use crate::domain::entities::Quote;
166 use chrono::Utc;
167 use rust_decimal::Decimal;
168 use std::str::FromStr;
169 use uuid::Uuid;
170
171 macro_rules! dec {
173 ($val:expr) => {
174 Decimal::from_str(stringify!($val)).unwrap()
175 };
176 }
177
178 #[test]
179 fn test_quote_response_dto_conversion() {
180 let building_id = Uuid::new_v4();
181 let contractor_id = Uuid::new_v4();
182 let validity_date = Utc::now() + chrono::Duration::days(30);
183
184 let quote = Quote::new(
185 building_id,
186 contractor_id,
187 "Roof Repair".to_string(),
188 "Repair leaking roof tiles".to_string(),
189 dec!(5000.00),
190 dec!(0.21),
191 validity_date,
192 14,
193 10,
194 )
195 .unwrap();
196
197 let dto = QuoteResponseDto::from(quote.clone());
198
199 assert_eq!(dto.id, quote.id.to_string());
200 assert_eq!(dto.project_title, "Roof Repair");
201 assert_eq!(dto.amount_excl_vat, "5000.00");
202 assert_eq!(dto.amount_incl_vat, "6050.00");
203 assert_eq!(dto.status, "Requested");
204 assert!(!dto.is_expired);
205 assert_eq!(dto.estimated_duration_days, 14);
206 assert_eq!(dto.warranty_years, 10);
207 }
208
209 #[test]
210 fn test_quote_score_dto_conversion() {
211 let quote_id = Uuid::new_v4();
212 let score = QuoteScore {
213 quote_id,
214 total_score: 75.5,
215 price_score: 80.0,
216 delay_score: 70.0,
217 warranty_score: 90.0,
218 reputation_score: 60.0,
219 };
220
221 let dto = QuoteScoreResponseDto::from(score.clone());
222
223 assert_eq!(dto.quote_id, quote_id.to_string());
224 assert_eq!(dto.total_score, 75.5);
225 assert_eq!(dto.price_score, 80.0);
226 assert_eq!(dto.delay_score, 70.0);
227 assert_eq!(dto.warranty_score, 90.0);
228 assert_eq!(dto.reputation_score, 60.0);
229 }
230}