1use crate::application::ports::AccountRepository;
11use crate::domain::entities::{Account, AccountType};
12use std::sync::Arc;
13use uuid::Uuid;
14
15pub struct AccountUseCases {
23 repository: Arc<dyn AccountRepository>,
24}
25
26impl AccountUseCases {
27 pub fn new(repository: Arc<dyn AccountRepository>) -> Self {
28 Self { repository }
29 }
30
31 pub async fn create_account(
44 &self,
45 code: String,
46 label: String,
47 parent_code: Option<String>,
48 account_type: AccountType,
49 direct_use: bool,
50 organization_id: Uuid,
51 ) -> Result<Account, String> {
52 if self.repository.exists(&code, organization_id).await? {
54 return Err(format!(
55 "Account code '{}' already exists for this organization",
56 code
57 ));
58 }
59
60 if let Some(ref parent) = parent_code {
62 if !self.repository.exists(parent, organization_id).await? {
63 return Err(format!("Parent account code '{}' does not exist", parent));
64 }
65 }
66
67 let account = Account::new(
69 code,
70 label,
71 parent_code,
72 account_type,
73 direct_use,
74 organization_id,
75 )?;
76
77 self.repository.create(&account).await
79 }
80
81 pub async fn get_account(&self, id: Uuid) -> Result<Option<Account>, String> {
83 self.repository.find_by_id(id).await
84 }
85
86 pub async fn get_account_by_code(
88 &self,
89 code: &str,
90 organization_id: Uuid,
91 ) -> Result<Option<Account>, String> {
92 self.repository.find_by_code(code, organization_id).await
93 }
94
95 pub async fn list_accounts(&self, organization_id: Uuid) -> Result<Vec<Account>, String> {
97 self.repository.find_by_organization(organization_id).await
98 }
99
100 pub async fn list_accounts_by_type(
102 &self,
103 account_type: AccountType,
104 organization_id: Uuid,
105 ) -> Result<Vec<Account>, String> {
106 self.repository
107 .find_by_type(account_type, organization_id)
108 .await
109 }
110
111 pub async fn list_child_accounts(
113 &self,
114 parent_code: &str,
115 organization_id: Uuid,
116 ) -> Result<Vec<Account>, String> {
117 self.repository
118 .find_by_parent_code(parent_code, organization_id)
119 .await
120 }
121
122 pub async fn list_direct_use_accounts(
124 &self,
125 organization_id: Uuid,
126 ) -> Result<Vec<Account>, String> {
127 self.repository
128 .find_direct_use_accounts(organization_id)
129 .await
130 }
131
132 pub async fn search_accounts(
134 &self,
135 code_pattern: &str,
136 organization_id: Uuid,
137 ) -> Result<Vec<Account>, String> {
138 self.repository
139 .search_by_code_pattern(code_pattern, organization_id)
140 .await
141 }
142
143 pub async fn update_account(
145 &self,
146 id: Uuid,
147 label: Option<String>,
148 parent_code: Option<Option<String>>,
149 account_type: Option<AccountType>,
150 direct_use: Option<bool>,
151 ) -> Result<Account, String> {
152 let mut account = self
153 .repository
154 .find_by_id(id)
155 .await?
156 .ok_or_else(|| "Account not found".to_string())?;
157
158 if let Some(Some(ref new_parent)) = parent_code {
160 if !self
161 .repository
162 .exists(new_parent, account.organization_id)
163 .await?
164 {
165 return Err(format!(
166 "Parent account code '{}' does not exist",
167 new_parent
168 ));
169 }
170 }
171
172 account.update(label, parent_code, account_type, direct_use)?;
173 self.repository.update(&account).await
174 }
175
176 pub async fn delete_account(&self, id: Uuid) -> Result<(), String> {
182 self.repository.delete(id).await
183 }
184
185 pub async fn count_accounts(&self, organization_id: Uuid) -> Result<i64, String> {
187 self.repository.count_by_organization(organization_id).await
188 }
189
190 pub async fn seed_belgian_pcmn(&self, organization_id: Uuid) -> Result<i64, String> {
210 let existing_count = self
212 .repository
213 .count_by_organization(organization_id)
214 .await?;
215 if existing_count > 0 {
216 return Err(format!(
217 "Organization already has {} accounts. Cannot seed PCMN.",
218 existing_count
219 ));
220 }
221
222 let accounts_data = get_belgian_pcmn_seed_data();
225
226 let mut created_count = 0i64;
227
228 for (code, label, parent_code, account_type, direct_use) in accounts_data {
229 let account = Account::new(
230 code.to_string(),
231 label.to_string(),
232 parent_code.map(|s| s.to_string()),
233 account_type,
234 direct_use,
235 organization_id,
236 )?;
237
238 self.repository.create(&account).await?;
239 created_count += 1;
240 }
241
242 Ok(created_count)
243 }
244}
245
246fn get_belgian_pcmn_seed_data() -> Vec<(
257 &'static str,
258 &'static str,
259 Option<&'static str>,
260 AccountType,
261 bool,
262)> {
263 vec![
264 (
268 "1",
269 "Fonds propres, provisions pour risques et charges",
270 None,
271 AccountType::Liability,
272 false,
273 ),
274 ("10", "Capital", Some("1"), AccountType::Liability, false),
275 (
276 "100",
277 "Capital souscrit",
278 Some("10"),
279 AccountType::Liability,
280 true,
281 ),
282 ("13", "Réserves", Some("1"), AccountType::Liability, false),
283 (
284 "130",
285 "Réserve légale",
286 Some("13"),
287 AccountType::Liability,
288 true,
289 ),
290 (
291 "131",
292 "Réserves disponibles",
293 Some("13"),
294 AccountType::Liability,
295 true,
296 ),
297 (
298 "14",
299 "Provisions pour risques et charges",
300 Some("1"),
301 AccountType::Liability,
302 true,
303 ),
304 ("2", "Actifs immobilisés", None, AccountType::Asset, false),
308 (
309 "22",
310 "Terrains et constructions",
311 Some("2"),
312 AccountType::Asset,
313 false,
314 ),
315 ("220", "Terrains", Some("22"), AccountType::Asset, true),
316 ("221", "Constructions", Some("22"), AccountType::Asset, true),
317 (
321 "4",
322 "Créances et dettes à un an au plus",
323 None,
324 AccountType::Asset,
325 false,
326 ),
327 (
329 "40",
330 "Créances commerciales",
331 Some("4"),
332 AccountType::Asset,
333 false,
334 ),
335 (
336 "400",
337 "Copropriétaires - Appels de fonds",
338 Some("40"),
339 AccountType::Asset,
340 true,
341 ),
342 (
343 "401",
344 "Copropriétaires - Charges courantes",
345 Some("40"),
346 AccountType::Asset,
347 true,
348 ),
349 (
350 "402",
351 "Copropriétaires - Travaux extraordinaires",
352 Some("40"),
353 AccountType::Asset,
354 true,
355 ),
356 (
357 "409",
358 "Réductions de valeur actées (provisions)",
359 Some("40"),
360 AccountType::Asset,
361 true,
362 ),
363 (
365 "44",
366 "Dettes commerciales",
367 Some("4"),
368 AccountType::Liability,
369 false,
370 ),
371 (
372 "440",
373 "Fournisseurs",
374 Some("44"),
375 AccountType::Liability,
376 true,
377 ),
378 (
379 "441",
380 "Effets à payer",
381 Some("44"),
382 AccountType::Liability,
383 true,
384 ),
385 (
387 "45",
388 "Dettes fiscales, salariales et sociales",
389 Some("4"),
390 AccountType::Liability,
391 false,
392 ),
393 (
394 "451",
395 "TVA à payer",
396 Some("45"),
397 AccountType::Liability,
398 true,
399 ),
400 (
401 "411",
402 "TVA récupérable",
403 Some("4"),
404 AccountType::Asset,
405 true,
406 ),
407 (
409 "46",
410 "Acomptes reçus",
411 Some("4"),
412 AccountType::Liability,
413 true,
414 ),
415 (
416 "47",
417 "Dettes diverses",
418 Some("4"),
419 AccountType::Liability,
420 true,
421 ),
422 (
426 "5",
427 "Placements de trésorerie et valeurs disponibles",
428 None,
429 AccountType::Asset,
430 false,
431 ),
432 (
433 "55",
434 "Établissements de crédit",
435 Some("5"),
436 AccountType::Asset,
437 false,
438 ),
439 (
440 "550",
441 "Compte courant bancaire",
442 Some("55"),
443 AccountType::Asset,
444 true,
445 ),
446 (
447 "551",
448 "Compte épargne",
449 Some("55"),
450 AccountType::Asset,
451 true,
452 ),
453 ("57", "Caisse", Some("5"), AccountType::Asset, true),
454 ("6", "Charges", None, AccountType::Expense, false),
458 (
460 "60",
461 "Approvisionnements et marchandises",
462 Some("6"),
463 AccountType::Expense,
464 false,
465 ),
466 (
467 "604",
468 "Achats de fournitures",
469 Some("60"),
470 AccountType::Expense,
471 false,
472 ),
473 (
474 "604001",
475 "Électricité",
476 Some("604"),
477 AccountType::Expense,
478 true,
479 ),
480 ("604002", "Eau", Some("604"), AccountType::Expense, true),
481 (
482 "604003",
483 "Gaz / Chauffage",
484 Some("604"),
485 AccountType::Expense,
486 true,
487 ),
488 ("604004", "Mazout", Some("604"), AccountType::Expense, true),
489 (
491 "61",
492 "Services et biens divers",
493 Some("6"),
494 AccountType::Expense,
495 false,
496 ),
497 (
498 "610",
499 "Loyers et charges locatives",
500 Some("61"),
501 AccountType::Expense,
502 false,
503 ),
504 (
505 "610001",
506 "Loyer local syndic",
507 Some("610"),
508 AccountType::Expense,
509 true,
510 ),
511 (
512 "610002",
513 "Charges locatives",
514 Some("610"),
515 AccountType::Expense,
516 true,
517 ),
518 (
519 "611",
520 "Entretien et réparations",
521 Some("61"),
522 AccountType::Expense,
523 false,
524 ),
525 (
526 "611001",
527 "Entretien bâtiment",
528 Some("611"),
529 AccountType::Expense,
530 true,
531 ),
532 (
533 "611002",
534 "Entretien ascenseur",
535 Some("611"),
536 AccountType::Expense,
537 true,
538 ),
539 (
540 "611003",
541 "Entretien chauffage",
542 Some("611"),
543 AccountType::Expense,
544 true,
545 ),
546 (
547 "611004",
548 "Entretien espaces verts",
549 Some("611"),
550 AccountType::Expense,
551 true,
552 ),
553 (
554 "611005",
555 "Nettoyage parties communes",
556 Some("611"),
557 AccountType::Expense,
558 true,
559 ),
560 (
561 "612",
562 "Fournitures faites à l'entreprise",
563 Some("61"),
564 AccountType::Expense,
565 false,
566 ),
567 (
568 "612001",
569 "Petit matériel",
570 Some("612"),
571 AccountType::Expense,
572 true,
573 ),
574 (
575 "612002",
576 "Produits d'entretien",
577 Some("612"),
578 AccountType::Expense,
579 true,
580 ),
581 (
582 "613",
583 "Rétributions de tiers",
584 Some("61"),
585 AccountType::Expense,
586 false,
587 ),
588 (
589 "613001",
590 "Honoraires syndic",
591 Some("613"),
592 AccountType::Expense,
593 true,
594 ),
595 (
596 "613002",
597 "Honoraires experts",
598 Some("613"),
599 AccountType::Expense,
600 true,
601 ),
602 (
603 "613003",
604 "Honoraires comptables",
605 Some("613"),
606 AccountType::Expense,
607 true,
608 ),
609 (
610 "613004",
611 "Honoraires avocats",
612 Some("613"),
613 AccountType::Expense,
614 true,
615 ),
616 (
617 "614",
618 "Publicité et propagande",
619 Some("61"),
620 AccountType::Expense,
621 true,
622 ),
623 ("615", "Assurances", Some("61"), AccountType::Expense, false),
624 (
625 "615001",
626 "Assurance incendie immeuble",
627 Some("615"),
628 AccountType::Expense,
629 true,
630 ),
631 (
632 "615002",
633 "Assurance responsabilité civile",
634 Some("615"),
635 AccountType::Expense,
636 true,
637 ),
638 (
639 "615003",
640 "Assurance tous risques",
641 Some("615"),
642 AccountType::Expense,
643 true,
644 ),
645 (
646 "617",
647 "Personnel intérimaire",
648 Some("61"),
649 AccountType::Expense,
650 true,
651 ),
652 (
653 "618",
654 "Rémunérations, charges sociales et pensions",
655 Some("61"),
656 AccountType::Expense,
657 false,
658 ),
659 (
660 "618001",
661 "Salaires personnel",
662 Some("618"),
663 AccountType::Expense,
664 true,
665 ),
666 (
667 "618002",
668 "Charges sociales",
669 Some("618"),
670 AccountType::Expense,
671 true,
672 ),
673 (
674 "618003",
675 "Assurances sociales",
676 Some("618"),
677 AccountType::Expense,
678 true,
679 ),
680 (
681 "619",
682 "Autres charges d'exploitation",
683 Some("61"),
684 AccountType::Expense,
685 false,
686 ),
687 (
688 "619001",
689 "Frais postaux",
690 Some("619"),
691 AccountType::Expense,
692 true,
693 ),
694 (
695 "619002",
696 "Frais bancaires",
697 Some("619"),
698 AccountType::Expense,
699 true,
700 ),
701 (
702 "619003",
703 "Taxes et impôts divers",
704 Some("619"),
705 AccountType::Expense,
706 true,
707 ),
708 (
710 "62",
711 "Amortissements, réductions de valeur",
712 Some("6"),
713 AccountType::Expense,
714 false,
715 ),
716 (
717 "620",
718 "Dotations aux amortissements",
719 Some("62"),
720 AccountType::Expense,
721 true,
722 ),
723 (
725 "63",
726 "Provisions pour risques et charges",
727 Some("6"),
728 AccountType::Expense,
729 false,
730 ),
731 (
732 "630",
733 "Dotations aux provisions",
734 Some("63"),
735 AccountType::Expense,
736 true,
737 ),
738 (
740 "64",
741 "Autres charges d'exploitation",
742 Some("6"),
743 AccountType::Expense,
744 true,
745 ),
746 (
747 "65",
748 "Charges financières",
749 Some("6"),
750 AccountType::Expense,
751 false,
752 ),
753 (
754 "650",
755 "Charges des dettes",
756 Some("65"),
757 AccountType::Expense,
758 true,
759 ),
760 (
761 "651",
762 "Réductions de valeur sur actifs circulants",
763 Some("65"),
764 AccountType::Expense,
765 true,
766 ),
767 (
769 "66",
770 "Charges exceptionnelles",
771 Some("6"),
772 AccountType::Expense,
773 true,
774 ),
775 (
776 "67",
777 "Impôts sur le résultat",
778 Some("6"),
779 AccountType::Expense,
780 true,
781 ),
782 ("7", "Produits", None, AccountType::Revenue, false),
786 (
788 "70",
789 "Chiffre d'affaires",
790 Some("7"),
791 AccountType::Revenue,
792 false,
793 ),
794 (
795 "700",
796 "Appels de fonds copropriétaires",
797 Some("70"),
798 AccountType::Revenue,
799 false,
800 ),
801 (
802 "700001",
803 "Appels de fonds ordinaires",
804 Some("700"),
805 AccountType::Revenue,
806 true,
807 ),
808 (
809 "700002",
810 "Appels de fonds extraordinaires",
811 Some("700"),
812 AccountType::Revenue,
813 true,
814 ),
815 (
816 "700003",
817 "Provisions mensuelles",
818 Some("700"),
819 AccountType::Revenue,
820 true,
821 ),
822 (
824 "74",
825 "Autres produits d'exploitation",
826 Some("7"),
827 AccountType::Revenue,
828 false,
829 ),
830 (
831 "740",
832 "Subsides d'exploitation",
833 Some("74"),
834 AccountType::Revenue,
835 true,
836 ),
837 (
838 "743",
839 "Indemnités perçues",
840 Some("74"),
841 AccountType::Revenue,
842 true,
843 ),
844 (
845 "744",
846 "Récupération charges antérieures",
847 Some("74"),
848 AccountType::Revenue,
849 true,
850 ),
851 (
853 "75",
854 "Produits financiers",
855 Some("7"),
856 AccountType::Revenue,
857 false,
858 ),
859 (
860 "750",
861 "Produits des immobilisations financières",
862 Some("75"),
863 AccountType::Revenue,
864 true,
865 ),
866 (
867 "751",
868 "Produits des actifs circulants",
869 Some("75"),
870 AccountType::Revenue,
871 false,
872 ),
873 (
874 "751001",
875 "Intérêts compte bancaire",
876 Some("751"),
877 AccountType::Revenue,
878 true,
879 ),
880 (
881 "751002",
882 "Intérêts compte épargne",
883 Some("751"),
884 AccountType::Revenue,
885 true,
886 ),
887 (
889 "76",
890 "Produits exceptionnels",
891 Some("7"),
892 AccountType::Revenue,
893 true,
894 ),
895 (
896 "77",
897 "Régularisation d'impôts",
898 Some("7"),
899 AccountType::Revenue,
900 true,
901 ),
902 (
906 "9",
907 "Comptes hors bilan",
908 None,
909 AccountType::OffBalance,
910 false,
911 ),
912 (
913 "90",
914 "Droits et engagements",
915 Some("9"),
916 AccountType::OffBalance,
917 true,
918 ),
919 ]
920}
921
922#[cfg(test)]
927mod tests {
928 use super::*;
929
930 #[test]
931 fn test_belgian_pcmn_seed_data_structure() {
932 let data = get_belgian_pcmn_seed_data();
933
934 assert!(data.len() >= 80, "Should have at least 80 accounts");
936
937 let codes: Vec<&str> = data.iter().map(|(code, _, _, _, _)| *code).collect();
939 assert!(codes.contains(&"1"), "Should have class 1 (Liabilities)");
940 assert!(codes.contains(&"6"), "Should have class 6 (Expenses)");
941 assert!(codes.contains(&"7"), "Should have class 7 (Revenue)");
942
943 assert!(codes.contains(&"604001"), "Should have Electricity account");
945 assert!(
946 codes.contains(&"611002"),
947 "Should have Elevator maintenance"
948 );
949 assert!(codes.contains(&"615001"), "Should have Building insurance");
950 assert!(
951 codes.contains(&"700001"),
952 "Should have Regular fees revenue"
953 );
954 }
955
956 #[test]
957 fn test_account_hierarchy_consistency() {
958 let data = get_belgian_pcmn_seed_data();
959 let codes: Vec<&str> = data.iter().map(|(code, _, _, _, _)| *code).collect();
960
961 for (code, _, parent_code, _, _) in &data {
963 if let Some(parent) = parent_code {
964 assert!(
965 codes.contains(parent),
966 "Account '{}' references non-existent parent '{}'",
967 code,
968 parent
969 );
970 }
971 }
972 }
973
974 #[test]
975 fn test_account_types_match_pcmn_classes() {
976 let data = get_belgian_pcmn_seed_data();
977
978 for (code, _, _, account_type, _) in &data {
979 let detected_type = AccountType::from_code(code);
980 if code.len() > 1 {
983 let _ = (account_type, detected_type); }
987 }
988 }
989}