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
246pub(crate) fn get_belgian_pcmn_seed_data() -> Vec<(
259 &'static str,
260 &'static str,
261 Option<&'static str>,
262 AccountType,
263 bool,
264)> {
265 vec![
266 (
270 "1",
271 "Fonds propres, provisions pour risques et charges",
272 None,
273 AccountType::Liability,
274 false,
275 ),
276 ("10", "Capital", Some("1"), AccountType::Liability, false),
277 (
278 "100",
279 "Capital souscrit",
280 Some("10"),
281 AccountType::Liability,
282 true,
283 ),
284 ("13", "Réserves", Some("1"), AccountType::Liability, false),
285 (
286 "130",
287 "Réserve légale",
288 Some("13"),
289 AccountType::Liability,
290 true,
291 ),
292 (
293 "131",
294 "Réserves disponibles",
295 Some("13"),
296 AccountType::Liability,
297 true,
298 ),
299 (
300 "14",
301 "Provisions pour risques et charges",
302 Some("1"),
303 AccountType::Liability,
304 true,
305 ),
306 ("2", "Actifs immobilisés", None, AccountType::Asset, false),
310 (
311 "22",
312 "Terrains et constructions",
313 Some("2"),
314 AccountType::Asset,
315 false,
316 ),
317 ("220", "Terrains", Some("22"), AccountType::Asset, true),
318 ("221", "Constructions", Some("22"), AccountType::Asset, true),
319 (
323 "4",
324 "Créances et dettes à un an au plus",
325 None,
326 AccountType::Asset,
327 false,
328 ),
329 (
331 "40",
332 "Créances commerciales",
333 Some("4"),
334 AccountType::Asset,
335 false,
336 ),
337 (
338 "400",
339 "Copropriétaires - Appels de fonds",
340 Some("40"),
341 AccountType::Asset,
342 true,
343 ),
344 (
345 "401",
346 "Copropriétaires - Charges courantes",
347 Some("40"),
348 AccountType::Asset,
349 true,
350 ),
351 (
352 "402",
353 "Copropriétaires - Travaux extraordinaires",
354 Some("40"),
355 AccountType::Asset,
356 true,
357 ),
358 (
359 "409",
360 "Réductions de valeur actées (provisions)",
361 Some("40"),
362 AccountType::Asset,
363 true,
364 ),
365 (
367 "44",
368 "Dettes commerciales",
369 Some("4"),
370 AccountType::Liability,
371 false,
372 ),
373 (
374 "440",
375 "Fournisseurs",
376 Some("44"),
377 AccountType::Liability,
378 true,
379 ),
380 (
381 "441",
382 "Effets à payer",
383 Some("44"),
384 AccountType::Liability,
385 true,
386 ),
387 (
389 "45",
390 "Dettes fiscales, salariales et sociales",
391 Some("4"),
392 AccountType::Liability,
393 false,
394 ),
395 (
396 "451",
397 "TVA à payer",
398 Some("45"),
399 AccountType::Liability,
400 true,
401 ),
402 (
403 "411",
404 "TVA récupérable",
405 Some("4"),
406 AccountType::Asset,
407 true,
408 ),
409 (
411 "46",
412 "Acomptes reçus",
413 Some("4"),
414 AccountType::Liability,
415 true,
416 ),
417 (
418 "47",
419 "Dettes diverses",
420 Some("4"),
421 AccountType::Liability,
422 true,
423 ),
424 (
428 "5",
429 "Placements de trésorerie et valeurs disponibles",
430 None,
431 AccountType::Asset,
432 false,
433 ),
434 (
435 "55",
436 "Établissements de crédit",
437 Some("5"),
438 AccountType::Asset,
439 false,
440 ),
441 (
442 "550",
443 "Compte courant bancaire",
444 Some("55"),
445 AccountType::Asset,
446 true,
447 ),
448 (
449 "551",
450 "Compte épargne",
451 Some("55"),
452 AccountType::Asset,
453 true,
454 ),
455 ("57", "Caisse", Some("5"), AccountType::Asset, true),
456 ("6", "Charges", None, AccountType::Expense, false),
460 (
462 "60",
463 "Approvisionnements et marchandises",
464 Some("6"),
465 AccountType::Expense,
466 false,
467 ),
468 (
469 "604",
470 "Achats de fournitures",
471 Some("60"),
472 AccountType::Expense,
473 false,
474 ),
475 (
476 "604001",
477 "Électricité",
478 Some("604"),
479 AccountType::Expense,
480 true,
481 ),
482 ("604002", "Eau", Some("604"), AccountType::Expense, true),
483 (
484 "604003",
485 "Gaz / Chauffage",
486 Some("604"),
487 AccountType::Expense,
488 true,
489 ),
490 ("604004", "Mazout", Some("604"), AccountType::Expense, true),
491 (
493 "61",
494 "Services et biens divers",
495 Some("6"),
496 AccountType::Expense,
497 false,
498 ),
499 (
500 "610",
501 "Loyers et charges locatives",
502 Some("61"),
503 AccountType::Expense,
504 false,
505 ),
506 (
507 "610001",
508 "Loyer local syndic",
509 Some("610"),
510 AccountType::Expense,
511 true,
512 ),
513 (
514 "610002",
515 "Charges locatives",
516 Some("610"),
517 AccountType::Expense,
518 true,
519 ),
520 (
521 "611",
522 "Entretien et réparations",
523 Some("61"),
524 AccountType::Expense,
525 false,
526 ),
527 (
528 "611001",
529 "Entretien bâtiment",
530 Some("611"),
531 AccountType::Expense,
532 true,
533 ),
534 (
535 "611002",
536 "Entretien ascenseur",
537 Some("611"),
538 AccountType::Expense,
539 true,
540 ),
541 (
542 "611003",
543 "Entretien chauffage",
544 Some("611"),
545 AccountType::Expense,
546 true,
547 ),
548 (
549 "611004",
550 "Entretien espaces verts",
551 Some("611"),
552 AccountType::Expense,
553 true,
554 ),
555 (
556 "611005",
557 "Nettoyage parties communes",
558 Some("611"),
559 AccountType::Expense,
560 true,
561 ),
562 (
563 "612",
564 "Fournitures faites à l'entreprise",
565 Some("61"),
566 AccountType::Expense,
567 false,
568 ),
569 (
570 "612001",
571 "Petit matériel",
572 Some("612"),
573 AccountType::Expense,
574 true,
575 ),
576 (
577 "612002",
578 "Produits d'entretien",
579 Some("612"),
580 AccountType::Expense,
581 true,
582 ),
583 (
584 "613",
585 "Rétributions de tiers",
586 Some("61"),
587 AccountType::Expense,
588 false,
589 ),
590 (
591 "613001",
592 "Honoraires syndic",
593 Some("613"),
594 AccountType::Expense,
595 true,
596 ),
597 (
598 "613002",
599 "Honoraires experts",
600 Some("613"),
601 AccountType::Expense,
602 true,
603 ),
604 (
605 "613003",
606 "Honoraires comptables",
607 Some("613"),
608 AccountType::Expense,
609 true,
610 ),
611 (
612 "613004",
613 "Honoraires avocats",
614 Some("613"),
615 AccountType::Expense,
616 true,
617 ),
618 (
619 "614",
620 "Publicité et propagande",
621 Some("61"),
622 AccountType::Expense,
623 true,
624 ),
625 ("615", "Assurances", Some("61"), AccountType::Expense, false),
626 (
627 "615001",
628 "Assurance incendie immeuble",
629 Some("615"),
630 AccountType::Expense,
631 true,
632 ),
633 (
634 "615002",
635 "Assurance responsabilité civile",
636 Some("615"),
637 AccountType::Expense,
638 true,
639 ),
640 (
641 "615003",
642 "Assurance tous risques",
643 Some("615"),
644 AccountType::Expense,
645 true,
646 ),
647 (
648 "617",
649 "Personnel intérimaire",
650 Some("61"),
651 AccountType::Expense,
652 true,
653 ),
654 (
655 "618",
656 "Rémunérations, charges sociales et pensions",
657 Some("61"),
658 AccountType::Expense,
659 false,
660 ),
661 (
662 "618001",
663 "Salaires personnel",
664 Some("618"),
665 AccountType::Expense,
666 true,
667 ),
668 (
669 "618002",
670 "Charges sociales",
671 Some("618"),
672 AccountType::Expense,
673 true,
674 ),
675 (
676 "618003",
677 "Assurances sociales",
678 Some("618"),
679 AccountType::Expense,
680 true,
681 ),
682 (
683 "619",
684 "Autres charges d'exploitation",
685 Some("61"),
686 AccountType::Expense,
687 false,
688 ),
689 (
690 "619001",
691 "Frais postaux",
692 Some("619"),
693 AccountType::Expense,
694 true,
695 ),
696 (
697 "619002",
698 "Frais bancaires",
699 Some("619"),
700 AccountType::Expense,
701 true,
702 ),
703 (
704 "619003",
705 "Taxes et impôts divers",
706 Some("619"),
707 AccountType::Expense,
708 true,
709 ),
710 (
712 "62",
713 "Amortissements, réductions de valeur",
714 Some("6"),
715 AccountType::Expense,
716 false,
717 ),
718 (
719 "620",
720 "Dotations aux amortissements",
721 Some("62"),
722 AccountType::Expense,
723 true,
724 ),
725 (
727 "63",
728 "Provisions pour risques et charges",
729 Some("6"),
730 AccountType::Expense,
731 false,
732 ),
733 (
734 "630",
735 "Dotations aux provisions",
736 Some("63"),
737 AccountType::Expense,
738 true,
739 ),
740 (
742 "64",
743 "Autres charges d'exploitation",
744 Some("6"),
745 AccountType::Expense,
746 true,
747 ),
748 (
749 "65",
750 "Charges financières",
751 Some("6"),
752 AccountType::Expense,
753 false,
754 ),
755 (
756 "650",
757 "Charges des dettes",
758 Some("65"),
759 AccountType::Expense,
760 true,
761 ),
762 (
763 "651",
764 "Réductions de valeur sur actifs circulants",
765 Some("65"),
766 AccountType::Expense,
767 true,
768 ),
769 (
771 "66",
772 "Charges exceptionnelles",
773 Some("6"),
774 AccountType::Expense,
775 true,
776 ),
777 (
778 "67",
779 "Impôts sur le résultat",
780 Some("6"),
781 AccountType::Expense,
782 true,
783 ),
784 ("7", "Produits", None, AccountType::Revenue, false),
788 (
790 "70",
791 "Chiffre d'affaires",
792 Some("7"),
793 AccountType::Revenue,
794 false,
795 ),
796 (
797 "700",
798 "Appels de fonds copropriétaires",
799 Some("70"),
800 AccountType::Revenue,
801 false,
802 ),
803 (
804 "700001",
805 "Appels de fonds ordinaires",
806 Some("700"),
807 AccountType::Revenue,
808 true,
809 ),
810 (
811 "700002",
812 "Appels de fonds extraordinaires",
813 Some("700"),
814 AccountType::Revenue,
815 true,
816 ),
817 (
818 "700003",
819 "Provisions mensuelles",
820 Some("700"),
821 AccountType::Revenue,
822 true,
823 ),
824 (
826 "74",
827 "Autres produits d'exploitation",
828 Some("7"),
829 AccountType::Revenue,
830 false,
831 ),
832 (
833 "740",
834 "Subsides d'exploitation",
835 Some("74"),
836 AccountType::Revenue,
837 true,
838 ),
839 (
840 "743",
841 "Indemnités perçues",
842 Some("74"),
843 AccountType::Revenue,
844 true,
845 ),
846 (
847 "744",
848 "Récupération charges antérieures",
849 Some("74"),
850 AccountType::Revenue,
851 true,
852 ),
853 (
855 "75",
856 "Produits financiers",
857 Some("7"),
858 AccountType::Revenue,
859 false,
860 ),
861 (
862 "750",
863 "Produits des immobilisations financières",
864 Some("75"),
865 AccountType::Revenue,
866 true,
867 ),
868 (
869 "751",
870 "Produits des actifs circulants",
871 Some("75"),
872 AccountType::Revenue,
873 false,
874 ),
875 (
876 "751001",
877 "Intérêts compte bancaire",
878 Some("751"),
879 AccountType::Revenue,
880 true,
881 ),
882 (
883 "751002",
884 "Intérêts compte épargne",
885 Some("751"),
886 AccountType::Revenue,
887 true,
888 ),
889 (
891 "76",
892 "Produits exceptionnels",
893 Some("7"),
894 AccountType::Revenue,
895 true,
896 ),
897 (
898 "77",
899 "Régularisation d'impôts",
900 Some("7"),
901 AccountType::Revenue,
902 true,
903 ),
904 (
908 "9",
909 "Comptes hors bilan",
910 None,
911 AccountType::OffBalance,
912 false,
913 ),
914 (
915 "90",
916 "Droits et engagements",
917 Some("9"),
918 AccountType::OffBalance,
919 true,
920 ),
921 ]
922}
923
924#[cfg(test)]
929mod tests {
930 use super::*;
931
932 #[test]
933 fn test_belgian_pcmn_seed_data_structure() {
934 let data = get_belgian_pcmn_seed_data();
935
936 assert!(data.len() >= 80, "Should have at least 80 accounts");
938
939 let codes: Vec<&str> = data.iter().map(|(code, _, _, _, _)| *code).collect();
941 assert!(codes.contains(&"1"), "Should have class 1 (Liabilities)");
942 assert!(codes.contains(&"6"), "Should have class 6 (Expenses)");
943 assert!(codes.contains(&"7"), "Should have class 7 (Revenue)");
944
945 assert!(codes.contains(&"604001"), "Should have Electricity account");
947 assert!(
948 codes.contains(&"611002"),
949 "Should have Elevator maintenance"
950 );
951 assert!(codes.contains(&"615001"), "Should have Building insurance");
952 assert!(
953 codes.contains(&"700001"),
954 "Should have Regular fees revenue"
955 );
956 }
957
958 #[test]
959 fn test_account_hierarchy_consistency() {
960 let data = get_belgian_pcmn_seed_data();
961 let codes: Vec<&str> = data.iter().map(|(code, _, _, _, _)| *code).collect();
962
963 for (code, _, parent_code, _, _) in &data {
965 if let Some(parent) = parent_code {
966 assert!(
967 codes.contains(parent),
968 "Account '{}' references non-existent parent '{}'",
969 code,
970 parent
971 );
972 }
973 }
974 }
975
976 #[test]
977 fn test_account_types_match_pcmn_classes() {
978 let data = get_belgian_pcmn_seed_data();
979
980 for (code, _, _, account_type, _) in &data {
981 let detected_type = AccountType::from_code(code);
982 if code.len() > 1 {
985 let _ = (account_type, detected_type); }
989 }
990 }
991}