Skip to main content

koprogo_api/application/use_cases/
account_use_cases.rs

1// Application Use Cases: Account Management
2//
3// CREDITS & ATTRIBUTION:
4// Business logic inspired by the Noalyss project (https://gitlab.com/noalyss/noalyss)
5// Noalyss is a free accounting software for Belgian and French accounting
6// License: GPL-2.0-or-later (GNU General Public License version 2 or later)
7// Copyright: (C) 1989, 1991 Free Software Foundation, Inc.
8// Copyright: Dany De Bontridder <dany@alchimerys.eu>
9
10use crate::application::ports::AccountRepository;
11use crate::domain::entities::{Account, AccountType};
12use std::sync::Arc;
13use uuid::Uuid;
14
15/// Use cases for managing accounts in the Belgian PCMN
16///
17/// This orchestrates account operations, including:
18/// - CRUD operations for accounts
19/// - Seeding Belgian PCMN chart of accounts (inspired by Noalyss mono-belge.sql)
20/// - Hierarchical account management
21/// - Account validation and business rules
22pub 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    /// Create a new account
32    ///
33    /// # Arguments
34    /// * `code` - Account code (e.g., "700", "604001")
35    /// * `label` - Account description
36    /// * `parent_code` - Optional parent account code
37    /// * `account_type` - Account classification
38    /// * `direct_use` - Whether account can be used in transactions
39    /// * `organization_id` - Organization ID
40    ///
41    /// # Returns
42    /// Created account or error message
43    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        // Validation: check if account code already exists
53        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        // Validation: if parent_code is specified, ensure it exists
61        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        // Create domain entity with validation
68        let account = Account::new(
69            code,
70            label,
71            parent_code,
72            account_type,
73            direct_use,
74            organization_id,
75        )?;
76
77        // Persist to database
78        self.repository.create(&account).await
79    }
80
81    /// Get account by ID
82    pub async fn get_account(&self, id: Uuid) -> Result<Option<Account>, String> {
83        self.repository.find_by_id(id).await
84    }
85
86    /// Get account by code within an organization
87    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    /// List all accounts for an organization
96    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    /// List accounts by type (for financial reports)
101    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    /// List child accounts of a parent
112    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    /// List accounts that can be used directly in transactions
123    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    /// Search accounts by code pattern (e.g., "60%" for all class 6 accounts)
133    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    /// Update an existing account
144    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        // Validation: if parent_code is being changed, ensure it exists
159        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    /// Delete an account
177    ///
178    /// Validates:
179    /// - Account has no children
180    /// - Account is not used in expenses
181    pub async fn delete_account(&self, id: Uuid) -> Result<(), String> {
182        self.repository.delete(id).await
183    }
184
185    /// Count accounts in an organization
186    pub async fn count_accounts(&self, organization_id: Uuid) -> Result<i64, String> {
187        self.repository.count_by_organization(organization_id).await
188    }
189
190    /// Seed Belgian PCMN (Plan Comptable Minimum Normalisé) for a new organization
191    ///
192    /// Creates a standard chart of accounts for Belgian property management.
193    /// This seed data is inspired by Noalyss' mono-belge.sql, curated for
194    /// property management (syndic de copropriété).
195    ///
196    /// # Arguments
197    /// * `organization_id` - Organization to seed accounts for
198    ///
199    /// # Returns
200    /// Number of accounts created or error message
201    ///
202    /// # Belgian PCMN Structure
203    /// - Class 1: Liabilities (Capital, Reserves)
204    /// - Classes 2-5: Assets (Fixed assets, Receivables, Bank)
205    /// - Class 6: Expenses (Electricity, Maintenance, Insurance, etc.)
206    /// - Class 7: Revenue (Regular fees, Extraordinary fees, Interest)
207    ///
208    /// Reference: Noalyss contrib/mono-dossier/mono-belge.sql
209    pub async fn seed_belgian_pcmn(&self, organization_id: Uuid) -> Result<i64, String> {
210        // Check if accounts already exist
211        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        // Belgian PCMN seed data inspired by Noalyss mono-belge.sql
223        // Curated for property management (copropriété/mede-eigendom)
224        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
246/// Belgian PCMN seed data for property management
247///
248/// Returns: Vec<(code, label, parent_code, account_type, direct_use)>
249///
250/// CREDITS: Inspired by Noalyss contrib/mono-dossier/mono-belge.sql
251/// License: GPL-2.0-or-later
252/// Copyright: Dany De Bontridder <dany@alchimerys.eu>
253///
254/// This is a curated subset of the Belgian PCMN relevant for property management.
255/// Full PCMN has 100+ accounts; we focus on the most common for syndic operations.
256/// `pub(crate)` : `ExpenseAccountingService` s'en sert pour prouver, en test,
257/// que les comptes qu'il utilise existent bel et bien dans le plan.
258pub(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        // ====================================================================
267        // CLASS 1: LIABILITIES (Capital, Reserves, Provisions)
268        // ====================================================================
269        (
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        // ====================================================================
307        // CLASS 2-3: FIXED ASSETS & INVENTORY (minimal for property mgmt)
308        // ====================================================================
309        ("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        // ====================================================================
320        // CLASS 4: RECEIVABLES & PAYABLES
321        // ====================================================================
322        (
323            "4",
324            "Créances et dettes à un an au plus",
325            None,
326            AccountType::Asset,
327            false,
328        ),
329        // Owners receivables (appels de fonds)
330        (
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        // Suppliers payables
366        (
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        // VAT
388        (
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        // Other receivables/payables
410        (
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        // ====================================================================
425        // CLASS 5: BANK & CASH
426        // ====================================================================
427        (
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        // ====================================================================
457        // CLASS 6: EXPENSES (Charges) - CORE FOR PROPERTY MANAGEMENT
458        // ====================================================================
459        ("6", "Charges", None, AccountType::Expense, false),
460        // Class 60: Purchases and inventory
461        (
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        // Class 61: Services and goods
492        (
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        // Class 62: Depreciation
711        (
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        // Class 63: Provisions
726        (
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        // Class 64-65: Financial expenses & Other
741        (
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        // Class 66-67: Exceptional & Tax expenses
770        (
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        // ====================================================================
785        // CLASS 7: REVENUE (Produits) - CORE FOR PROPERTY MANAGEMENT
786        // ====================================================================
787        ("7", "Produits", None, AccountType::Revenue, false),
788        // Class 70: Operating revenue (appels de fonds)
789        (
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        // Class 74: Other operating revenue
825        (
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        // Class 75: Financial revenue
854        (
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        // Class 76-77: Exceptional & Other revenue
890        (
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        // ====================================================================
905        // CLASS 9: OFF-BALANCE (Memorandum accounts)
906        // ====================================================================
907        (
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// ============================================================================
925// UNIT TESTS
926// ============================================================================
927
928#[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        // Should have substantial number of accounts
937        assert!(data.len() >= 80, "Should have at least 80 accounts");
938
939        // Check root accounts exist
940        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        // Check essential property management accounts
946        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 each account with a parent, ensure parent exists in the list
964        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            // Parent accounts might have different types than detected
983            // This is OK, we're just checking consistency for leaf accounts
984            if code.len() > 1 {
985                // For detailed accounts, type should generally match detection
986                // (Some exceptions exist for special accounts)
987                let _ = (account_type, detected_type); // Just ensure no panic
988            }
989        }
990    }
991}