koprogo_api/domain/entities/
owner_credit_balance.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12pub struct OwnerCreditBalance {
13 pub owner_id: Uuid,
14 pub building_id: Uuid,
15 pub credits_earned: i32, pub credits_spent: i32, pub balance: i32, pub total_exchanges: i32, pub average_rating: Option<f32>, pub created_at: DateTime<Utc>,
21 pub updated_at: DateTime<Utc>,
22}
23
24impl OwnerCreditBalance {
25 pub fn new(owner_id: Uuid, building_id: Uuid) -> Self {
27 let now = Utc::now();
28
29 OwnerCreditBalance {
30 owner_id,
31 building_id,
32 credits_earned: 0,
33 credits_spent: 0,
34 balance: 0,
35 total_exchanges: 0,
36 average_rating: None,
37 created_at: now,
38 updated_at: now,
39 }
40 }
41
42 pub fn earn_credits(&mut self, amount: i32) -> Result<(), String> {
44 if amount <= 0 {
45 return Err("Credits to earn must be positive".to_string());
46 }
47
48 self.credits_earned += amount;
49 self.balance += amount;
50 self.updated_at = Utc::now();
51
52 Ok(())
53 }
54
55 pub fn spend_credits(&mut self, amount: i32) -> Result<(), String> {
57 if amount <= 0 {
58 return Err("Credits to spend must be positive".to_string());
59 }
60
61 self.credits_spent += amount;
64 self.balance -= amount;
65 self.updated_at = Utc::now();
66
67 Ok(())
68 }
69
70 pub fn increment_exchanges(&mut self) {
72 self.total_exchanges += 1;
73 self.updated_at = Utc::now();
74 }
75
76 pub fn update_rating(&mut self, new_rating: f32) -> Result<(), String> {
78 if !(1.0..=5.0).contains(&new_rating) {
79 return Err("Rating must be between 1.0 and 5.0".to_string());
80 }
81
82 self.average_rating = Some(new_rating);
83 self.updated_at = Utc::now();
84
85 Ok(())
86 }
87
88 pub fn has_sufficient_credits(&self, required: i32) -> bool {
90 self.balance >= required
91 }
92
93 pub fn credit_status(&self) -> CreditStatus {
95 if self.balance > 0 {
96 CreditStatus::Positive
97 } else if self.balance < 0 {
98 CreditStatus::Negative
99 } else {
100 CreditStatus::Balanced
101 }
102 }
103
104 pub fn is_new_member(&self) -> bool {
106 self.total_exchanges == 0
107 }
108
109 pub fn participation_level(&self) -> ParticipationLevel {
111 match self.total_exchanges {
112 0 => ParticipationLevel::New,
113 1..=5 => ParticipationLevel::Beginner,
114 6..=20 => ParticipationLevel::Active,
115 21..=50 => ParticipationLevel::Veteran,
116 _ => ParticipationLevel::Expert,
117 }
118 }
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
122pub enum CreditStatus {
123 Positive, Balanced, Negative, }
127
128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
129pub enum ParticipationLevel {
130 New, Beginner, Active, Veteran, Expert, }
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn test_new_credit_balance() {
143 let owner_id = Uuid::new_v4();
144 let building_id = Uuid::new_v4();
145
146 let balance = OwnerCreditBalance::new(owner_id, building_id);
147
148 assert_eq!(balance.owner_id, owner_id);
149 assert_eq!(balance.building_id, building_id);
150 assert_eq!(balance.credits_earned, 0);
151 assert_eq!(balance.credits_spent, 0);
152 assert_eq!(balance.balance, 0);
153 assert_eq!(balance.total_exchanges, 0);
154 assert!(balance.average_rating.is_none());
155 assert_eq!(balance.credit_status(), CreditStatus::Balanced);
156 assert!(balance.is_new_member());
157 }
158
159 #[test]
160 fn test_earn_credits() {
161 let owner_id = Uuid::new_v4();
162 let building_id = Uuid::new_v4();
163
164 let mut balance = OwnerCreditBalance::new(owner_id, building_id);
165
166 assert!(balance.earn_credits(5).is_ok());
168 assert_eq!(balance.credits_earned, 5);
169 assert_eq!(balance.balance, 5);
170 assert_eq!(balance.credit_status(), CreditStatus::Positive);
171
172 assert!(balance.earn_credits(3).is_ok());
174 assert_eq!(balance.credits_earned, 8);
175 assert_eq!(balance.balance, 8);
176 }
177
178 #[test]
179 fn test_spend_credits() {
180 let owner_id = Uuid::new_v4();
181 let building_id = Uuid::new_v4();
182
183 let mut balance = OwnerCreditBalance::new(owner_id, building_id);
184
185 balance.earn_credits(10).unwrap();
187
188 assert!(balance.spend_credits(6).is_ok());
190 assert_eq!(balance.credits_spent, 6);
191 assert_eq!(balance.balance, 4);
192 assert_eq!(balance.credit_status(), CreditStatus::Positive);
193
194 assert!(balance.spend_credits(5).is_ok());
196 assert_eq!(balance.credits_spent, 11);
197 assert_eq!(balance.balance, -1);
198 assert_eq!(balance.credit_status(), CreditStatus::Negative);
199 }
200
201 #[test]
202 fn test_earn_credits_validation() {
203 let owner_id = Uuid::new_v4();
204 let building_id = Uuid::new_v4();
205
206 let mut balance = OwnerCreditBalance::new(owner_id, building_id);
207
208 assert!(balance.earn_credits(0).is_err());
210
211 assert!(balance.earn_credits(-5).is_err());
213 }
214
215 #[test]
216 fn test_spend_credits_validation() {
217 let owner_id = Uuid::new_v4();
218 let building_id = Uuid::new_v4();
219
220 let mut balance = OwnerCreditBalance::new(owner_id, building_id);
221
222 assert!(balance.spend_credits(0).is_err());
224
225 assert!(balance.spend_credits(-5).is_err());
227 }
228
229 #[test]
230 fn test_increment_exchanges() {
231 let owner_id = Uuid::new_v4();
232 let building_id = Uuid::new_v4();
233
234 let mut balance = OwnerCreditBalance::new(owner_id, building_id);
235
236 balance.increment_exchanges();
237 assert_eq!(balance.total_exchanges, 1);
238
239 balance.increment_exchanges();
240 assert_eq!(balance.total_exchanges, 2);
241 }
242
243 #[test]
244 fn test_update_rating() {
245 let owner_id = Uuid::new_v4();
246 let building_id = Uuid::new_v4();
247
248 let mut balance = OwnerCreditBalance::new(owner_id, building_id);
249
250 assert!(balance.update_rating(4.5).is_ok());
252 assert_eq!(balance.average_rating, Some(4.5));
253
254 assert!(balance.update_rating(0.5).is_err());
256
257 assert!(balance.update_rating(5.5).is_err());
259 }
260
261 #[test]
262 fn test_has_sufficient_credits() {
263 let owner_id = Uuid::new_v4();
264 let building_id = Uuid::new_v4();
265
266 let mut balance = OwnerCreditBalance::new(owner_id, building_id);
267
268 balance.earn_credits(10).unwrap();
269
270 assert!(balance.has_sufficient_credits(5));
271 assert!(balance.has_sufficient_credits(10));
272 assert!(!balance.has_sufficient_credits(11));
273 }
274
275 #[test]
276 fn test_participation_levels() {
277 let owner_id = Uuid::new_v4();
278 let building_id = Uuid::new_v4();
279
280 let mut balance = OwnerCreditBalance::new(owner_id, building_id);
281
282 assert_eq!(balance.participation_level(), ParticipationLevel::New);
284
285 for _ in 0..3 {
287 balance.increment_exchanges();
288 }
289 assert_eq!(balance.participation_level(), ParticipationLevel::Beginner);
290
291 for _ in 0..10 {
293 balance.increment_exchanges();
294 }
295 assert_eq!(balance.participation_level(), ParticipationLevel::Active);
296
297 for _ in 0..15 {
299 balance.increment_exchanges();
300 }
301 assert_eq!(balance.participation_level(), ParticipationLevel::Veteran);
302
303 for _ in 0..25 {
305 balance.increment_exchanges();
306 }
307 assert_eq!(balance.participation_level(), ParticipationLevel::Expert);
308 }
309
310 #[test]
311 fn test_credit_status() {
312 let owner_id = Uuid::new_v4();
313 let building_id = Uuid::new_v4();
314
315 let mut balance = OwnerCreditBalance::new(owner_id, building_id);
316
317 assert_eq!(balance.credit_status(), CreditStatus::Balanced);
319
320 balance.earn_credits(5).unwrap();
322 assert_eq!(balance.credit_status(), CreditStatus::Positive);
323
324 balance.spend_credits(10).unwrap();
326 assert_eq!(balance.credit_status(), CreditStatus::Negative);
327 }
328}