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