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