1use crate::application::dto::{
2 CreateEtatDateRequest, EtatDateResponse, EtatDateStatsResponse, PageRequest,
3 UpdateEtatDateAdditionalDataRequest, UpdateEtatDateFinancialRequest,
4};
5use crate::application::ports::{
6 BuildingRepository, EtatDateRepository, UnitOwnerRepository, UnitRepository,
7};
8use crate::domain::entities::{EtatDate, EtatDateStatus};
9use f64;
10use std::sync::Arc;
11use uuid::Uuid;
12
13pub struct EtatDateUseCases {
14 repository: Arc<dyn EtatDateRepository>,
15 unit_repository: Arc<dyn UnitRepository>,
16 building_repository: Arc<dyn BuildingRepository>,
17 unit_owner_repository: Arc<dyn UnitOwnerRepository>,
18}
19
20impl EtatDateUseCases {
21 pub fn new(
22 repository: Arc<dyn EtatDateRepository>,
23 unit_repository: Arc<dyn UnitRepository>,
24 building_repository: Arc<dyn BuildingRepository>,
25 unit_owner_repository: Arc<dyn UnitOwnerRepository>,
26 ) -> Self {
27 Self {
28 repository,
29 unit_repository,
30 building_repository,
31 unit_owner_repository,
32 }
33 }
34
35 pub async fn create_etat_date(
37 &self,
38 request: CreateEtatDateRequest,
39 ) -> Result<EtatDateResponse, String> {
40 let unit = self
42 .unit_repository
43 .find_by_id(request.unit_id)
44 .await?
45 .ok_or_else(|| "Unit not found".to_string())?;
46
47 let building = self
49 .building_repository
50 .find_by_id(request.building_id)
51 .await?
52 .ok_or_else(|| "Building not found".to_string())?;
53
54 let unit_owners = self
56 .unit_owner_repository
57 .find_current_owners_by_unit(request.unit_id)
58 .await?;
59
60 if unit_owners.is_empty() {
61 return Err("Unit has no active owners".to_string());
62 }
63
64 let total_quota: f64 = unit_owners.iter().map(|uo| uo.ownership_percentage).sum();
66
67 let ordinary_charges_quota = total_quota * 100.0; let extraordinary_charges_quota = ordinary_charges_quota;
71
72 let etat_date = EtatDate::new(
74 request.organization_id,
75 request.building_id,
76 request.unit_id,
77 request.reference_date,
78 request.language,
79 request.notary_name,
80 request.notary_email,
81 request.notary_phone,
82 building.name.clone(),
83 building.address.clone(),
84 unit.unit_number.clone(),
85 unit.floor.map(|f| f.to_string()),
86 Some(unit.surface_area),
87 ordinary_charges_quota,
88 extraordinary_charges_quota,
89 )?;
90
91 let created = self.repository.create(&etat_date).await?;
92 Ok(EtatDateResponse::from(created))
93 }
94
95 pub async fn get_etat_date(&self, id: Uuid) -> Result<Option<EtatDateResponse>, String> {
97 let etat_date = self.repository.find_by_id(id).await?;
98 Ok(etat_date.map(EtatDateResponse::from))
99 }
100
101 pub async fn get_by_reference_number(
103 &self,
104 reference_number: &str,
105 ) -> Result<Option<EtatDateResponse>, String> {
106 let etat_date = self
107 .repository
108 .find_by_reference_number(reference_number)
109 .await?;
110 Ok(etat_date.map(EtatDateResponse::from))
111 }
112
113 pub async fn list_by_unit(&self, unit_id: Uuid) -> Result<Vec<EtatDateResponse>, String> {
115 let etats = self.repository.find_by_unit(unit_id).await?;
116 Ok(etats.into_iter().map(EtatDateResponse::from).collect())
117 }
118
119 pub async fn list_by_building(
121 &self,
122 building_id: Uuid,
123 ) -> Result<Vec<EtatDateResponse>, String> {
124 let etats = self.repository.find_by_building(building_id).await?;
125 Ok(etats.into_iter().map(EtatDateResponse::from).collect())
126 }
127
128 pub async fn list_paginated(
130 &self,
131 page_request: &PageRequest,
132 organization_id: Option<Uuid>,
133 status: Option<EtatDateStatus>,
134 ) -> Result<(Vec<EtatDateResponse>, i64), String> {
135 let (etats, total) = self
136 .repository
137 .find_all_paginated(page_request, organization_id, status)
138 .await?;
139
140 let dtos = etats.into_iter().map(EtatDateResponse::from).collect();
141 Ok((dtos, total))
142 }
143
144 pub async fn mark_in_progress(&self, id: Uuid) -> Result<EtatDateResponse, String> {
146 let mut etat_date = self
147 .repository
148 .find_by_id(id)
149 .await?
150 .ok_or_else(|| "État daté not found".to_string())?;
151
152 etat_date.mark_in_progress()?;
153
154 let updated = self.repository.update(&etat_date).await?;
155 Ok(EtatDateResponse::from(updated))
156 }
157
158 pub async fn mark_generated(
160 &self,
161 id: Uuid,
162 pdf_file_path: String,
163 ) -> Result<EtatDateResponse, String> {
164 let mut etat_date = self
165 .repository
166 .find_by_id(id)
167 .await?
168 .ok_or_else(|| "État daté not found".to_string())?;
169
170 etat_date.mark_generated(pdf_file_path)?;
171
172 let updated = self.repository.update(&etat_date).await?;
173 Ok(EtatDateResponse::from(updated))
174 }
175
176 pub async fn mark_delivered(&self, id: Uuid) -> Result<EtatDateResponse, String> {
178 let mut etat_date = self
179 .repository
180 .find_by_id(id)
181 .await?
182 .ok_or_else(|| "État daté not found".to_string())?;
183
184 etat_date.mark_delivered()?;
185
186 let updated = self.repository.update(&etat_date).await?;
187 Ok(EtatDateResponse::from(updated))
188 }
189
190 pub async fn update_financial_data(
192 &self,
193 id: Uuid,
194 request: UpdateEtatDateFinancialRequest,
195 ) -> Result<EtatDateResponse, String> {
196 let mut etat_date = self
197 .repository
198 .find_by_id(id)
199 .await?
200 .ok_or_else(|| "État daté not found".to_string())?;
201
202 etat_date.update_financial_data(
203 request.owner_balance,
204 request.arrears_amount,
205 request.monthly_provision_amount,
206 request.total_balance,
207 request.approved_works_unpaid,
208 )?;
209
210 let updated = self.repository.update(&etat_date).await?;
211 Ok(EtatDateResponse::from(updated))
212 }
213
214 pub async fn update_additional_data(
216 &self,
217 id: Uuid,
218 request: UpdateEtatDateAdditionalDataRequest,
219 ) -> Result<EtatDateResponse, String> {
220 let mut etat_date = self
221 .repository
222 .find_by_id(id)
223 .await?
224 .ok_or_else(|| "État daté not found".to_string())?;
225
226 etat_date.update_additional_data(request.additional_data)?;
227
228 let updated = self.repository.update(&etat_date).await?;
229 Ok(EtatDateResponse::from(updated))
230 }
231
232 pub async fn list_overdue(
234 &self,
235 organization_id: Uuid,
236 ) -> Result<Vec<EtatDateResponse>, String> {
237 let etats = self.repository.find_overdue(organization_id).await?;
238 Ok(etats.into_iter().map(EtatDateResponse::from).collect())
239 }
240
241 pub async fn list_expired(
243 &self,
244 organization_id: Uuid,
245 ) -> Result<Vec<EtatDateResponse>, String> {
246 let etats = self.repository.find_expired(organization_id).await?;
247 Ok(etats.into_iter().map(EtatDateResponse::from).collect())
248 }
249
250 pub async fn delete_etat_date(&self, id: Uuid) -> Result<bool, String> {
252 self.repository.delete(id).await
253 }
254
255 pub async fn get_stats(&self, organization_id: Uuid) -> Result<EtatDateStatsResponse, String> {
257 self.repository.get_stats(organization_id).await
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use crate::application::dto::EtatDateStatsResponse;
265 use crate::application::ports::{
266 BuildingRepository, EtatDateRepository, UnitOwnerRepository, UnitRepository,
267 };
268 use crate::domain::entities::{
269 Building, EtatDate, EtatDateLanguage, EtatDateStatus, Unit, UnitOwner, UnitType,
270 };
271 use chrono::Utc;
272 use mockall::mock;
273 use mockall::predicate::*;
274
275 mock! {
278 pub EtatDateRepo {}
279
280 #[async_trait::async_trait]
281 impl EtatDateRepository for EtatDateRepo {
282 async fn create(&self, etat_date: &EtatDate) -> Result<EtatDate, String>;
283 async fn find_by_id(&self, id: Uuid) -> Result<Option<EtatDate>, String>;
284 async fn find_by_reference_number(&self, reference_number: &str) -> Result<Option<EtatDate>, String>;
285 async fn find_by_unit(&self, unit_id: Uuid) -> Result<Vec<EtatDate>, String>;
286 async fn find_by_building(&self, building_id: Uuid) -> Result<Vec<EtatDate>, String>;
287 async fn find_all_paginated(
288 &self,
289 page_request: &PageRequest,
290 organization_id: Option<Uuid>,
291 status: Option<EtatDateStatus>,
292 ) -> Result<(Vec<EtatDate>, i64), String>;
293 async fn find_overdue(&self, organization_id: Uuid) -> Result<Vec<EtatDate>, String>;
294 async fn find_expired(&self, organization_id: Uuid) -> Result<Vec<EtatDate>, String>;
295 async fn update(&self, etat_date: &EtatDate) -> Result<EtatDate, String>;
296 async fn delete(&self, id: Uuid) -> Result<bool, String>;
297 async fn get_stats(&self, organization_id: Uuid) -> Result<EtatDateStatsResponse, String>;
298 async fn count_by_status(&self, organization_id: Uuid, status: EtatDateStatus) -> Result<i64, String>;
299 }
300 }
301
302 mock! {
303 pub UnitRepo {}
304
305 #[async_trait::async_trait]
306 impl UnitRepository for UnitRepo {
307 async fn create(&self, unit: &Unit) -> Result<Unit, String>;
308 async fn find_by_id(&self, id: Uuid) -> Result<Option<Unit>, String>;
309 async fn find_by_building(&self, building_id: Uuid) -> Result<Vec<Unit>, String>;
310 async fn find_by_owner(&self, owner_id: Uuid) -> Result<Vec<Unit>, String>;
311 async fn find_all_paginated(
312 &self,
313 page_request: &crate::application::dto::PageRequest,
314 filters: &crate::application::dto::UnitFilters,
315 ) -> Result<(Vec<Unit>, i64), String>;
316 async fn update(&self, unit: &Unit) -> Result<Unit, String>;
317 async fn delete(&self, id: Uuid) -> Result<bool, String>;
318 }
319 }
320
321 mock! {
322 pub BuildingRepo {}
323
324 #[async_trait::async_trait]
325 impl BuildingRepository for BuildingRepo {
326 async fn create(&self, building: &Building) -> Result<Building, String>;
327 async fn find_by_id(&self, id: Uuid) -> Result<Option<Building>, String>;
328 async fn find_by_slug(&self, slug: &str) -> Result<Option<Building>, String>;
329 async fn find_all(&self) -> Result<Vec<Building>, String>;
330 async fn find_all_paginated(
331 &self,
332 page_request: &crate::application::dto::PageRequest,
333 filters: &crate::application::dto::BuildingFilters,
334 ) -> Result<(Vec<Building>, i64), String>;
335 async fn update(&self, building: &Building) -> Result<Building, String>;
336 async fn delete(&self, id: Uuid) -> Result<bool, String>;
337 }
338 }
339
340 mock! {
341 pub UnitOwnerRepo {}
342
343 #[async_trait::async_trait]
344 impl UnitOwnerRepository for UnitOwnerRepo {
345 async fn create(&self, unit_owner: &UnitOwner) -> Result<UnitOwner, String>;
346 async fn find_by_id(&self, id: Uuid) -> Result<Option<UnitOwner>, String>;
347 async fn find_current_owners_by_unit(&self, unit_id: Uuid) -> Result<Vec<UnitOwner>, String>;
348 async fn find_current_units_by_owner(&self, owner_id: Uuid) -> Result<Vec<UnitOwner>, String>;
349 async fn find_all_owners_by_unit(&self, unit_id: Uuid) -> Result<Vec<UnitOwner>, String>;
350 async fn find_all_units_by_owner(&self, owner_id: Uuid) -> Result<Vec<UnitOwner>, String>;
351 async fn update(&self, unit_owner: &UnitOwner) -> Result<UnitOwner, String>;
352 async fn delete(&self, id: Uuid) -> Result<(), String>;
353 async fn has_active_owners(&self, unit_id: Uuid) -> Result<bool, String>;
354 async fn get_total_ownership_percentage(&self, unit_id: Uuid) -> Result<f64, String>;
355 async fn find_active_by_unit_and_owner(&self, unit_id: Uuid, owner_id: Uuid) -> Result<Option<UnitOwner>, String>;
356 async fn find_active_by_building(&self, building_id: Uuid) -> Result<Vec<(Uuid, Uuid, f64)>, String>;
357 }
358 }
359
360 fn make_building(org_id: Uuid) -> Building {
363 Building::new(
364 org_id,
365 "Résidence Les Jardins".to_string(),
366 "Rue de la Loi 123".to_string(),
367 "Bruxelles".to_string(),
368 "1000".to_string(),
369 "Belgium".to_string(),
370 25,
371 1000,
372 Some(2020),
373 )
374 .unwrap()
375 }
376
377 fn make_unit(org_id: Uuid, building_id: Uuid) -> Unit {
378 Unit::new(
379 org_id,
380 building_id,
381 "101".to_string(),
382 UnitType::Apartment,
383 Some(1),
384 85.0,
385 50.0,
386 )
387 .unwrap()
388 }
389
390 fn make_unit_owner(unit_id: Uuid) -> UnitOwner {
391 UnitOwner::new(unit_id, Uuid::new_v4(), 0.5, true).unwrap()
392 }
393
394 fn make_etat_date(org_id: Uuid, building_id: Uuid, unit_id: Uuid) -> EtatDate {
395 EtatDate::new(
396 org_id,
397 building_id,
398 unit_id,
399 Utc::now(),
400 EtatDateLanguage::Fr,
401 "Maitre Dupont".to_string(),
402 "dupont@notaire.be".to_string(),
403 Some("+32 2 123 4567".to_string()),
404 "Residence Les Jardins".to_string(),
405 "Rue de la Loi 123, 1000 Bruxelles".to_string(),
406 "101".to_string(),
407 Some("1".to_string()),
408 Some(85.0),
409 50.0,
410 50.0,
411 )
412 .unwrap()
413 }
414
415 fn make_create_request(
416 org_id: Uuid,
417 building_id: Uuid,
418 unit_id: Uuid,
419 ) -> CreateEtatDateRequest {
420 CreateEtatDateRequest {
421 organization_id: org_id,
422 building_id,
423 unit_id,
424 reference_date: Utc::now(),
425 language: EtatDateLanguage::Fr,
426 notary_name: "Maitre Dupont".to_string(),
427 notary_email: "dupont@notaire.be".to_string(),
428 notary_phone: Some("+32 2 123 4567".to_string()),
429 }
430 }
431
432 fn build_use_cases(
433 etat_repo: MockEtatDateRepo,
434 unit_repo: MockUnitRepo,
435 building_repo: MockBuildingRepo,
436 uo_repo: MockUnitOwnerRepo,
437 ) -> EtatDateUseCases {
438 EtatDateUseCases::new(
439 Arc::new(etat_repo),
440 Arc::new(unit_repo),
441 Arc::new(building_repo),
442 Arc::new(uo_repo),
443 )
444 }
445
446 #[tokio::test]
449 async fn test_create_etat_date_success() {
450 let org_id = Uuid::new_v4();
451 let building_id = Uuid::new_v4();
452 let unit_id = Uuid::new_v4();
453
454 let mut etat_repo = MockEtatDateRepo::new();
455 let mut unit_repo = MockUnitRepo::new();
456 let mut building_repo = MockBuildingRepo::new();
457 let mut uo_repo = MockUnitOwnerRepo::new();
458
459 let unit = make_unit(org_id, building_id);
461 unit_repo
462 .expect_find_by_id()
463 .with(eq(unit_id))
464 .times(1)
465 .returning(move |_| Ok(Some(unit.clone())));
466
467 let building = make_building(org_id);
469 building_repo
470 .expect_find_by_id()
471 .with(eq(building_id))
472 .times(1)
473 .returning(move |_| Ok(Some(building.clone())));
474
475 let uo = make_unit_owner(unit_id);
477 uo_repo
478 .expect_find_current_owners_by_unit()
479 .with(eq(unit_id))
480 .times(1)
481 .returning(move |_| Ok(vec![uo.clone()]));
482
483 etat_repo
485 .expect_create()
486 .times(1)
487 .returning(|ed| Ok(ed.clone()));
488
489 let use_cases = build_use_cases(etat_repo, unit_repo, building_repo, uo_repo);
490 let request = make_create_request(org_id, building_id, unit_id);
491
492 let result = use_cases.create_etat_date(request).await;
493 assert!(result.is_ok());
494 let response = result.unwrap();
495 assert_eq!(response.status, EtatDateStatus::Requested);
496 assert!(response.reference_number.starts_with("ED-"));
497 assert_eq!(response.notary_name, "Maitre Dupont");
498 assert_eq!(response.notary_email, "dupont@notaire.be");
499 }
500
501 #[tokio::test]
502 async fn test_create_etat_date_unit_not_found() {
503 let org_id = Uuid::new_v4();
504 let building_id = Uuid::new_v4();
505 let unit_id = Uuid::new_v4();
506
507 let etat_repo = MockEtatDateRepo::new();
508 let mut unit_repo = MockUnitRepo::new();
509 let building_repo = MockBuildingRepo::new();
510 let uo_repo = MockUnitOwnerRepo::new();
511
512 unit_repo
514 .expect_find_by_id()
515 .with(eq(unit_id))
516 .times(1)
517 .returning(|_| Ok(None));
518
519 let use_cases = build_use_cases(etat_repo, unit_repo, building_repo, uo_repo);
520 let request = make_create_request(org_id, building_id, unit_id);
521
522 let result = use_cases.create_etat_date(request).await;
523 assert!(result.is_err());
524 assert_eq!(result.unwrap_err(), "Unit not found");
525 }
526
527 #[tokio::test]
528 async fn test_create_etat_date_no_active_owners() {
529 let org_id = Uuid::new_v4();
530 let building_id = Uuid::new_v4();
531 let unit_id = Uuid::new_v4();
532
533 let etat_repo = MockEtatDateRepo::new();
534 let mut unit_repo = MockUnitRepo::new();
535 let mut building_repo = MockBuildingRepo::new();
536 let mut uo_repo = MockUnitOwnerRepo::new();
537
538 let unit = make_unit(org_id, building_id);
539 unit_repo
540 .expect_find_by_id()
541 .with(eq(unit_id))
542 .times(1)
543 .returning(move |_| Ok(Some(unit.clone())));
544
545 let building = make_building(org_id);
546 building_repo
547 .expect_find_by_id()
548 .with(eq(building_id))
549 .times(1)
550 .returning(move |_| Ok(Some(building.clone())));
551
552 uo_repo
554 .expect_find_current_owners_by_unit()
555 .with(eq(unit_id))
556 .times(1)
557 .returning(|_| Ok(vec![]));
558
559 let use_cases = build_use_cases(etat_repo, unit_repo, building_repo, uo_repo);
560 let request = make_create_request(org_id, building_id, unit_id);
561
562 let result = use_cases.create_etat_date(request).await;
563 assert!(result.is_err());
564 assert_eq!(result.unwrap_err(), "Unit has no active owners");
565 }
566
567 #[tokio::test]
568 async fn test_find_by_id_success() {
569 let org_id = Uuid::new_v4();
570 let building_id = Uuid::new_v4();
571 let unit_id = Uuid::new_v4();
572 let etat_date = make_etat_date(org_id, building_id, unit_id);
573 let etat_id = etat_date.id;
574
575 let mut etat_repo = MockEtatDateRepo::new();
576 etat_repo
577 .expect_find_by_id()
578 .with(eq(etat_id))
579 .times(1)
580 .returning(move |_| Ok(Some(etat_date.clone())));
581
582 let use_cases = build_use_cases(
583 etat_repo,
584 MockUnitRepo::new(),
585 MockBuildingRepo::new(),
586 MockUnitOwnerRepo::new(),
587 );
588
589 let result = use_cases.get_etat_date(etat_id).await;
590 assert!(result.is_ok());
591 let response = result.unwrap();
592 assert!(response.is_some());
593 assert_eq!(response.unwrap().id, etat_id);
594 }
595
596 #[tokio::test]
597 async fn test_find_by_reference_number_success() {
598 let org_id = Uuid::new_v4();
599 let building_id = Uuid::new_v4();
600 let unit_id = Uuid::new_v4();
601 let etat_date = make_etat_date(org_id, building_id, unit_id);
602 let ref_number = etat_date.reference_number.clone();
603
604 let mut etat_repo = MockEtatDateRepo::new();
605 etat_repo
606 .expect_find_by_reference_number()
607 .with(eq(ref_number.clone()))
608 .times(1)
609 .returning(move |_| Ok(Some(etat_date.clone())));
610
611 let use_cases = build_use_cases(
612 etat_repo,
613 MockUnitRepo::new(),
614 MockBuildingRepo::new(),
615 MockUnitOwnerRepo::new(),
616 );
617
618 let result = use_cases.get_by_reference_number(&ref_number).await;
619 assert!(result.is_ok());
620 let response = result.unwrap();
621 assert!(response.is_some());
622 assert_eq!(response.unwrap().reference_number, ref_number);
623 }
624
625 #[tokio::test]
626 async fn test_mark_in_progress_success() {
627 let org_id = Uuid::new_v4();
628 let building_id = Uuid::new_v4();
629 let unit_id = Uuid::new_v4();
630 let etat_date = make_etat_date(org_id, building_id, unit_id);
631 let etat_id = etat_date.id;
632
633 let mut etat_repo = MockEtatDateRepo::new();
634
635 etat_repo
637 .expect_find_by_id()
638 .with(eq(etat_id))
639 .times(1)
640 .returning(move |_| Ok(Some(etat_date.clone())));
641
642 etat_repo
644 .expect_update()
645 .times(1)
646 .returning(|ed| Ok(ed.clone()));
647
648 let use_cases = build_use_cases(
649 etat_repo,
650 MockUnitRepo::new(),
651 MockBuildingRepo::new(),
652 MockUnitOwnerRepo::new(),
653 );
654
655 let result = use_cases.mark_in_progress(etat_id).await;
656 assert!(result.is_ok());
657 let response = result.unwrap();
658 assert_eq!(response.status, EtatDateStatus::InProgress);
659 }
660
661 #[tokio::test]
662 async fn test_mark_generated_with_pdf_path() {
663 let org_id = Uuid::new_v4();
664 let building_id = Uuid::new_v4();
665 let unit_id = Uuid::new_v4();
666 let mut etat_date = make_etat_date(org_id, building_id, unit_id);
667 etat_date.status = EtatDateStatus::InProgress;
669 let etat_id = etat_date.id;
670
671 let mut etat_repo = MockEtatDateRepo::new();
672
673 etat_repo
674 .expect_find_by_id()
675 .with(eq(etat_id))
676 .times(1)
677 .returning(move |_| Ok(Some(etat_date.clone())));
678
679 etat_repo
680 .expect_update()
681 .times(1)
682 .returning(|ed| Ok(ed.clone()));
683
684 let use_cases = build_use_cases(
685 etat_repo,
686 MockUnitRepo::new(),
687 MockBuildingRepo::new(),
688 MockUnitOwnerRepo::new(),
689 );
690
691 let pdf_path = "/documents/etat-date/ED-2026-001.pdf".to_string();
692 let result = use_cases.mark_generated(etat_id, pdf_path.clone()).await;
693 assert!(result.is_ok());
694 let response = result.unwrap();
695 assert_eq!(response.status, EtatDateStatus::Generated);
696 assert_eq!(response.pdf_file_path, Some(pdf_path));
697 assert!(response.generated_date.is_some());
698 }
699
700 #[tokio::test]
701 async fn test_mark_delivered_to_notary() {
702 let org_id = Uuid::new_v4();
703 let building_id = Uuid::new_v4();
704 let unit_id = Uuid::new_v4();
705 let mut etat_date = make_etat_date(org_id, building_id, unit_id);
706 etat_date.status = EtatDateStatus::Generated;
708 etat_date.generated_date = Some(Utc::now());
709 etat_date.pdf_file_path = Some("/documents/etat-date/ED-2026-001.pdf".to_string());
710 let etat_id = etat_date.id;
711
712 let mut etat_repo = MockEtatDateRepo::new();
713
714 etat_repo
715 .expect_find_by_id()
716 .with(eq(etat_id))
717 .times(1)
718 .returning(move |_| Ok(Some(etat_date.clone())));
719
720 etat_repo
721 .expect_update()
722 .times(1)
723 .returning(|ed| Ok(ed.clone()));
724
725 let use_cases = build_use_cases(
726 etat_repo,
727 MockUnitRepo::new(),
728 MockBuildingRepo::new(),
729 MockUnitOwnerRepo::new(),
730 );
731
732 let result = use_cases.mark_delivered(etat_id).await;
733 assert!(result.is_ok());
734 let response = result.unwrap();
735 assert_eq!(response.status, EtatDateStatus::Delivered);
736 assert!(response.delivered_date.is_some());
737 }
738
739 #[tokio::test]
740 async fn test_update_financial_data_success() {
741 let org_id = Uuid::new_v4();
742 let building_id = Uuid::new_v4();
743 let unit_id = Uuid::new_v4();
744 let etat_date = make_etat_date(org_id, building_id, unit_id);
745 let etat_id = etat_date.id;
746
747 let mut etat_repo = MockEtatDateRepo::new();
748
749 etat_repo
750 .expect_find_by_id()
751 .with(eq(etat_id))
752 .times(1)
753 .returning(move |_| Ok(Some(etat_date.clone())));
754
755 etat_repo
756 .expect_update()
757 .times(1)
758 .returning(|ed| Ok(ed.clone()));
759
760 let use_cases = build_use_cases(
761 etat_repo,
762 MockUnitRepo::new(),
763 MockBuildingRepo::new(),
764 MockUnitOwnerRepo::new(),
765 );
766
767 let request = UpdateEtatDateFinancialRequest {
768 owner_balance: -1250.50,
769 arrears_amount: 800.0,
770 monthly_provision_amount: 150.0,
771 total_balance: -1250.50,
772 approved_works_unpaid: 3500.0,
773 };
774
775 let result = use_cases.update_financial_data(etat_id, request).await;
776 assert!(result.is_ok());
777 let response = result.unwrap();
778 assert_eq!(response.owner_balance, -1250.50);
779 assert_eq!(response.arrears_amount, 800.0);
780 assert_eq!(response.monthly_provision_amount, 150.0);
781 assert_eq!(response.total_balance, -1250.50);
782 assert_eq!(response.approved_works_unpaid, 3500.0);
783 }
784
785 #[tokio::test]
786 async fn test_list_overdue_returns_old_requests() {
787 let org_id = Uuid::new_v4();
788 let building_id = Uuid::new_v4();
789 let unit_id = Uuid::new_v4();
790
791 let mut overdue_etat = make_etat_date(org_id, building_id, unit_id);
792 overdue_etat.requested_date = Utc::now() - chrono::Duration::days(16);
794
795 let mut etat_repo = MockEtatDateRepo::new();
796 etat_repo
797 .expect_find_overdue()
798 .with(eq(org_id))
799 .times(1)
800 .returning(move |_| Ok(vec![overdue_etat.clone()]));
801
802 let use_cases = build_use_cases(
803 etat_repo,
804 MockUnitRepo::new(),
805 MockBuildingRepo::new(),
806 MockUnitOwnerRepo::new(),
807 );
808
809 let result = use_cases.list_overdue(org_id).await;
810 assert!(result.is_ok());
811 let items = result.unwrap();
812 assert_eq!(items.len(), 1);
813 assert!(items[0].is_overdue);
814 assert!(items[0].days_since_request >= 16);
815 }
816
817 #[tokio::test]
818 async fn test_list_expired_returns_old_etats() {
819 let org_id = Uuid::new_v4();
820 let building_id = Uuid::new_v4();
821 let unit_id = Uuid::new_v4();
822
823 let mut expired_etat = make_etat_date(org_id, building_id, unit_id);
824 expired_etat.reference_date = Utc::now() - chrono::Duration::days(100);
826
827 let mut etat_repo = MockEtatDateRepo::new();
828 etat_repo
829 .expect_find_expired()
830 .with(eq(org_id))
831 .times(1)
832 .returning(move |_| Ok(vec![expired_etat.clone()]));
833
834 let use_cases = build_use_cases(
835 etat_repo,
836 MockUnitRepo::new(),
837 MockBuildingRepo::new(),
838 MockUnitOwnerRepo::new(),
839 );
840
841 let result = use_cases.list_expired(org_id).await;
842 assert!(result.is_ok());
843 let items = result.unwrap();
844 assert_eq!(items.len(), 1);
845 assert!(items[0].is_expired);
846 }
847
848 #[tokio::test]
849 async fn test_mark_in_progress_not_found() {
850 let etat_id = Uuid::new_v4();
851
852 let mut etat_repo = MockEtatDateRepo::new();
853 etat_repo
854 .expect_find_by_id()
855 .with(eq(etat_id))
856 .times(1)
857 .returning(|_| Ok(None));
858
859 let use_cases = build_use_cases(
860 etat_repo,
861 MockUnitRepo::new(),
862 MockBuildingRepo::new(),
863 MockUnitOwnerRepo::new(),
864 );
865
866 let result = use_cases.mark_in_progress(etat_id).await;
867 assert!(result.is_err());
868 assert!(result.unwrap_err().contains("not found"));
869 }
870}