ringkernel_accnet/fabric/
chart_of_accounts.rs

1//! Chart of Accounts templates for different industries.
2//!
3//! Each template provides a realistic account structure with
4//! typical account relationships and expected flow patterns.
5
6use super::{CompanyArchetype, Industry};
7use crate::models::{AccountMetadata, AccountNode, AccountSemantics, AccountType};
8use uuid::Uuid;
9
10/// A Chart of Accounts template.
11#[derive(Debug, Clone)]
12pub struct ChartOfAccountsTemplate {
13    /// Industry this template is designed for
14    pub industry: Industry,
15    /// Template name
16    pub name: String,
17    /// Account definitions
18    pub accounts: Vec<AccountDefinition>,
19    /// Expected flow patterns between accounts
20    pub expected_flows: Vec<ExpectedFlow>,
21}
22
23/// Definition of an account in the template.
24#[derive(Debug, Clone)]
25pub struct AccountDefinition {
26    /// Account code (e.g., "1100")
27    pub code: String,
28    /// Account name
29    pub name: String,
30    /// Account type
31    pub account_type: AccountType,
32    /// Account class (for grouping)
33    pub class_id: u8,
34    /// Account subclass
35    pub subclass_id: u8,
36    /// Parent account code (for hierarchy)
37    pub parent_code: Option<String>,
38    /// Semantic flags
39    pub semantics: u32,
40    /// Typical activity level (transactions per month)
41    pub typical_activity: f32,
42    /// Description
43    pub description: String,
44}
45
46impl AccountDefinition {
47    /// Create a new account definition.
48    pub fn new(
49        code: impl Into<String>,
50        name: impl Into<String>,
51        account_type: AccountType,
52    ) -> Self {
53        Self {
54            code: code.into(),
55            name: name.into(),
56            account_type,
57            class_id: 0,
58            subclass_id: 0,
59            parent_code: None,
60            semantics: 0,
61            typical_activity: 10.0,
62            description: String::new(),
63        }
64    }
65
66    /// Set the class and subclass.
67    pub fn with_class(mut self, class_id: u8, subclass_id: u8) -> Self {
68        self.class_id = class_id;
69        self.subclass_id = subclass_id;
70        self
71    }
72
73    /// Set the parent account.
74    pub fn with_parent(mut self, parent: impl Into<String>) -> Self {
75        self.parent_code = Some(parent.into());
76        self
77    }
78
79    /// Set semantic flags.
80    pub fn with_semantics(mut self, semantics: u32) -> Self {
81        self.semantics = semantics;
82        self
83    }
84
85    /// Set typical activity level.
86    pub fn with_activity(mut self, activity: f32) -> Self {
87        self.typical_activity = activity;
88        self
89    }
90
91    /// Set description.
92    pub fn with_description(mut self, desc: impl Into<String>) -> Self {
93        self.description = desc.into();
94        self
95    }
96
97    /// Convert to AccountNode and AccountMetadata.
98    pub fn to_account(&self, index: u16) -> (AccountNode, AccountMetadata) {
99        let mut node = AccountNode::new(Uuid::new_v4(), self.account_type, index);
100        node.class_id = self.class_id;
101        node.subclass_id = self.subclass_id;
102
103        let mut metadata = AccountMetadata::new(&self.code, &self.name);
104        metadata.description = self.description.clone();
105        metadata.semantics = AccountSemantics {
106            flags: self.semantics,
107            typical_frequency: self.typical_activity,
108            avg_amount_scale: 50.0, // Default mid-range
109        };
110
111        (node, metadata)
112    }
113}
114
115/// An expected flow pattern between accounts.
116#[derive(Debug, Clone)]
117pub struct ExpectedFlow {
118    /// Source account code
119    pub from_code: String,
120    /// Target account code
121    pub to_code: String,
122    /// Relative frequency (0.0 - 1.0)
123    pub frequency: f64,
124    /// Typical amount range
125    pub amount_range: (f64, f64),
126    /// Description of this flow
127    pub description: String,
128}
129
130impl ExpectedFlow {
131    /// Create a new expected flow.
132    pub fn new(
133        from: impl Into<String>,
134        to: impl Into<String>,
135        frequency: f64,
136        amount_range: (f64, f64),
137    ) -> Self {
138        Self {
139            from_code: from.into(),
140            to_code: to.into(),
141            frequency,
142            amount_range,
143            description: String::new(),
144        }
145    }
146
147    /// Add description.
148    pub fn with_description(mut self, desc: impl Into<String>) -> Self {
149        self.description = desc.into();
150        self
151    }
152}
153
154impl ChartOfAccountsTemplate {
155    /// Create a minimal GAAP-compliant template (for education).
156    pub fn gaap_minimal() -> Self {
157        Self {
158            industry: Industry::Retail,
159            name: "GAAP Minimal".to_string(),
160            accounts: vec![
161                // Assets (1xxx)
162                AccountDefinition::new("1000", "Assets", AccountType::Asset)
163                    .with_class(1, 0)
164                    .with_description("Total assets"),
165                AccountDefinition::new("1100", "Cash", AccountType::Asset)
166                    .with_class(1, 1)
167                    .with_parent("1000")
168                    .with_semantics(AccountSemantics::IS_CASH)
169                    .with_activity(100.0)
170                    .with_description("Cash and cash equivalents"),
171                AccountDefinition::new("1200", "Accounts Receivable", AccountType::Asset)
172                    .with_class(1, 2)
173                    .with_parent("1000")
174                    .with_semantics(AccountSemantics::IS_RECEIVABLE)
175                    .with_activity(50.0)
176                    .with_description("Amounts owed by customers"),
177                AccountDefinition::new("1300", "Inventory", AccountType::Asset)
178                    .with_class(1, 3)
179                    .with_parent("1000")
180                    .with_semantics(AccountSemantics::IS_INVENTORY)
181                    .with_activity(30.0)
182                    .with_description("Goods held for sale"),
183                AccountDefinition::new("1400", "Prepaid Expenses", AccountType::Asset)
184                    .with_class(1, 4)
185                    .with_parent("1000")
186                    .with_activity(5.0)
187                    .with_description("Expenses paid in advance"),
188                AccountDefinition::new("1500", "Fixed Assets", AccountType::Asset)
189                    .with_class(1, 5)
190                    .with_parent("1000")
191                    .with_activity(2.0)
192                    .with_description("Property, plant, and equipment"),
193                AccountDefinition::new("1510", "Accumulated Depreciation", AccountType::Contra)
194                    .with_class(1, 5)
195                    .with_parent("1500")
196                    .with_semantics(AccountSemantics::IS_DEPRECIATION)
197                    .with_activity(12.0)
198                    .with_description("Accumulated depreciation on fixed assets"),
199                // Liabilities (2xxx)
200                AccountDefinition::new("2000", "Liabilities", AccountType::Liability)
201                    .with_class(2, 0)
202                    .with_description("Total liabilities"),
203                AccountDefinition::new("2100", "Accounts Payable", AccountType::Liability)
204                    .with_class(2, 1)
205                    .with_parent("2000")
206                    .with_semantics(AccountSemantics::IS_PAYABLE)
207                    .with_activity(40.0)
208                    .with_description("Amounts owed to suppliers"),
209                AccountDefinition::new("2200", "Accrued Expenses", AccountType::Liability)
210                    .with_class(2, 2)
211                    .with_parent("2000")
212                    .with_activity(12.0)
213                    .with_description("Expenses incurred but not yet paid"),
214                AccountDefinition::new("2300", "Unearned Revenue", AccountType::Liability)
215                    .with_class(2, 3)
216                    .with_parent("2000")
217                    .with_activity(10.0)
218                    .with_description("Revenue received but not yet earned"),
219                AccountDefinition::new("2400", "Notes Payable", AccountType::Liability)
220                    .with_class(2, 4)
221                    .with_parent("2000")
222                    .with_activity(2.0)
223                    .with_description("Short-term loans"),
224                // Equity (3xxx)
225                AccountDefinition::new("3000", "Equity", AccountType::Equity)
226                    .with_class(3, 0)
227                    .with_description("Owner's equity"),
228                AccountDefinition::new("3100", "Common Stock", AccountType::Equity)
229                    .with_class(3, 1)
230                    .with_parent("3000")
231                    .with_activity(1.0)
232                    .with_description("Issued common shares"),
233                AccountDefinition::new("3200", "Retained Earnings", AccountType::Equity)
234                    .with_class(3, 2)
235                    .with_parent("3000")
236                    .with_activity(1.0)
237                    .with_description("Accumulated profits"),
238                // Revenue (4xxx)
239                AccountDefinition::new("4000", "Revenue", AccountType::Revenue)
240                    .with_class(4, 0)
241                    .with_semantics(AccountSemantics::IS_REVENUE)
242                    .with_description("Total revenue"),
243                AccountDefinition::new("4100", "Sales Revenue", AccountType::Revenue)
244                    .with_class(4, 1)
245                    .with_parent("4000")
246                    .with_semantics(AccountSemantics::IS_REVENUE)
247                    .with_activity(100.0)
248                    .with_description("Revenue from product sales"),
249                AccountDefinition::new("4200", "Service Revenue", AccountType::Revenue)
250                    .with_class(4, 2)
251                    .with_parent("4000")
252                    .with_semantics(AccountSemantics::IS_REVENUE)
253                    .with_activity(20.0)
254                    .with_description("Revenue from services"),
255                // Expenses (5xxx-6xxx)
256                AccountDefinition::new("5000", "Cost of Goods Sold", AccountType::Expense)
257                    .with_class(5, 0)
258                    .with_semantics(AccountSemantics::IS_COGS)
259                    .with_activity(80.0)
260                    .with_description("Direct cost of products sold"),
261                AccountDefinition::new("6000", "Operating Expenses", AccountType::Expense)
262                    .with_class(6, 0)
263                    .with_semantics(AccountSemantics::IS_EXPENSE)
264                    .with_description("General operating expenses"),
265                AccountDefinition::new("6100", "Salaries Expense", AccountType::Expense)
266                    .with_class(6, 1)
267                    .with_parent("6000")
268                    .with_semantics(AccountSemantics::IS_PAYROLL)
269                    .with_activity(24.0)
270                    .with_description("Employee salaries"),
271                AccountDefinition::new("6200", "Rent Expense", AccountType::Expense)
272                    .with_class(6, 2)
273                    .with_parent("6000")
274                    .with_activity(12.0)
275                    .with_description("Rent for facilities"),
276                AccountDefinition::new("6300", "Utilities Expense", AccountType::Expense)
277                    .with_class(6, 3)
278                    .with_parent("6000")
279                    .with_activity(12.0)
280                    .with_description("Electricity, water, gas"),
281                AccountDefinition::new("6400", "Depreciation Expense", AccountType::Expense)
282                    .with_class(6, 4)
283                    .with_parent("6000")
284                    .with_activity(12.0)
285                    .with_description("Depreciation of fixed assets"),
286                AccountDefinition::new("6500", "Marketing Expense", AccountType::Expense)
287                    .with_class(6, 5)
288                    .with_parent("6000")
289                    .with_activity(20.0)
290                    .with_description("Advertising and marketing"),
291                AccountDefinition::new("6900", "Other Expenses", AccountType::Expense)
292                    .with_class(6, 9)
293                    .with_parent("6000")
294                    .with_activity(15.0)
295                    .with_description("Miscellaneous expenses"),
296                // Suspense/Clearing (9xxx)
297                AccountDefinition::new("9100", "Clearing Account", AccountType::Asset)
298                    .with_class(9, 1)
299                    .with_semantics(AccountSemantics::IS_SUSPENSE)
300                    .with_activity(30.0)
301                    .with_description("Temporary clearing account"),
302            ],
303            expected_flows: vec![
304                // Sales cycle
305                ExpectedFlow::new("4100", "1200", 0.30, (50.0, 5000.0))
306                    .with_description("Credit sale: Revenue → A/R"),
307                ExpectedFlow::new("1200", "1100", 0.25, (50.0, 5000.0))
308                    .with_description("Collection: A/R → Cash"),
309                ExpectedFlow::new("4100", "1100", 0.10, (20.0, 500.0))
310                    .with_description("Cash sale: Revenue → Cash"),
311                // Purchasing cycle
312                ExpectedFlow::new("1300", "2100", 0.15, (100.0, 10000.0))
313                    .with_description("Purchase: Inventory → A/P"),
314                ExpectedFlow::new("2100", "1100", 0.15, (100.0, 10000.0))
315                    .with_description("Payment: A/P → Cash"),
316                ExpectedFlow::new("5000", "1300", 0.15, (50.0, 5000.0))
317                    .with_description("Cost of sale: COGS → Inventory"),
318                // Operating expenses
319                ExpectedFlow::new("6100", "1100", 0.08, (5000.0, 50000.0))
320                    .with_description("Payroll: Salaries → Cash"),
321                ExpectedFlow::new("6200", "1100", 0.04, (1000.0, 10000.0))
322                    .with_description("Rent payment"),
323                ExpectedFlow::new("6300", "1100", 0.04, (200.0, 2000.0))
324                    .with_description("Utilities payment"),
325                ExpectedFlow::new("6400", "1510", 0.04, (500.0, 5000.0))
326                    .with_description("Depreciation: Expense → Accum Depr"),
327            ],
328        }
329    }
330
331    /// Create a retail-specific template with ~150 accounts.
332    pub fn retail_standard() -> Self {
333        let mut template = Self::gaap_minimal();
334        template.industry = Industry::Retail;
335        template.name = "Retail Standard".to_string();
336
337        // Add retail-specific accounts
338        template.accounts.extend(vec![
339            // More detailed inventory
340            AccountDefinition::new("1310", "Merchandise Inventory", AccountType::Asset)
341                .with_class(1, 3)
342                .with_parent("1300")
343                .with_semantics(AccountSemantics::IS_INVENTORY)
344                .with_activity(50.0),
345            AccountDefinition::new("1320", "Inventory in Transit", AccountType::Asset)
346                .with_class(1, 3)
347                .with_parent("1300")
348                .with_activity(10.0),
349            // Point of Sale
350            AccountDefinition::new("1110", "Cash Registers", AccountType::Asset)
351                .with_class(1, 1)
352                .with_parent("1100")
353                .with_semantics(AccountSemantics::IS_CASH)
354                .with_activity(200.0),
355            AccountDefinition::new("1120", "Undeposited Funds", AccountType::Asset)
356                .with_class(1, 1)
357                .with_parent("1100")
358                .with_semantics(AccountSemantics::IS_SUSPENSE | AccountSemantics::IS_CASH)
359                .with_activity(100.0),
360            // Credit card processing
361            AccountDefinition::new("1130", "Credit Card Receivable", AccountType::Asset)
362                .with_class(1, 1)
363                .with_parent("1100")
364                .with_activity(80.0),
365            // Sales returns
366            AccountDefinition::new("4110", "Sales Returns", AccountType::Contra)
367                .with_class(4, 1)
368                .with_parent("4100")
369                .with_activity(20.0),
370            AccountDefinition::new("4120", "Sales Discounts", AccountType::Contra)
371                .with_class(4, 1)
372                .with_parent("4100")
373                .with_activity(15.0),
374            // Retail expenses
375            AccountDefinition::new("6510", "Store Supplies", AccountType::Expense)
376                .with_class(6, 5)
377                .with_parent("6500")
378                .with_activity(10.0),
379            AccountDefinition::new("6520", "Credit Card Fees", AccountType::Expense)
380                .with_class(6, 5)
381                .with_parent("6500")
382                .with_activity(30.0),
383            AccountDefinition::new("6530", "Shrinkage", AccountType::Expense)
384                .with_class(6, 5)
385                .with_parent("6500")
386                .with_activity(12.0),
387        ]);
388
389        // Add retail-specific flows
390        template.expected_flows.extend(vec![
391            ExpectedFlow::new("1110", "1120", 0.20, (100.0, 5000.0))
392                .with_description("Register → Undeposited"),
393            ExpectedFlow::new("1120", "1100", 0.15, (1000.0, 20000.0))
394                .with_description("Bank deposit"),
395            ExpectedFlow::new("4110", "1200", 0.05, (20.0, 500.0)).with_description("Sales return"),
396            ExpectedFlow::new("6520", "1130", 0.08, (50.0, 500.0))
397                .with_description("CC processing fees"),
398        ]);
399
400        template
401    }
402
403    /// Create a SaaS-specific template.
404    pub fn saas_standard() -> Self {
405        let mut template = Self::gaap_minimal();
406        template.industry = Industry::SaaS;
407        template.name = "SaaS Standard".to_string();
408
409        // Replace/add SaaS-specific accounts
410        template.accounts.extend(vec![
411            // Deferred revenue is big in SaaS
412            AccountDefinition::new("2310", "Deferred Revenue - Current", AccountType::Liability)
413                .with_class(2, 3)
414                .with_parent("2300")
415                .with_activity(100.0),
416            AccountDefinition::new(
417                "2320",
418                "Deferred Revenue - Long-term",
419                AccountType::Liability,
420            )
421            .with_class(2, 3)
422            .with_parent("2300")
423            .with_activity(20.0),
424            // Subscription metrics
425            AccountDefinition::new("4110", "Subscription Revenue", AccountType::Revenue)
426                .with_class(4, 1)
427                .with_parent("4100")
428                .with_semantics(AccountSemantics::IS_REVENUE)
429                .with_activity(200.0),
430            AccountDefinition::new("4120", "Professional Services", AccountType::Revenue)
431                .with_class(4, 2)
432                .with_parent("4200")
433                .with_activity(20.0),
434            // SaaS costs
435            AccountDefinition::new("5100", "Hosting Costs", AccountType::Expense)
436                .with_class(5, 1)
437                .with_parent("5000")
438                .with_activity(12.0),
439            AccountDefinition::new("5200", "Third-party Software", AccountType::Expense)
440                .with_class(5, 2)
441                .with_parent("5000")
442                .with_activity(24.0),
443            // Customer acquisition
444            AccountDefinition::new("6510", "Customer Acquisition Cost", AccountType::Expense)
445                .with_class(6, 5)
446                .with_parent("6500")
447                .with_activity(50.0),
448            AccountDefinition::new("1410", "Deferred Commission", AccountType::Asset)
449                .with_class(1, 4)
450                .with_parent("1400")
451                .with_activity(30.0),
452        ]);
453
454        // SaaS-specific flows
455        template.expected_flows = vec![
456            ExpectedFlow::new("1100", "2310", 0.25, (500.0, 50000.0))
457                .with_description("Annual subscription cash → Deferred revenue"),
458            ExpectedFlow::new("2310", "4110", 0.30, (50.0, 5000.0))
459                .with_description("Revenue recognition"),
460            ExpectedFlow::new("4110", "1200", 0.10, (100.0, 10000.0))
461                .with_description("Invoiced subscription"),
462            ExpectedFlow::new("1200", "1100", 0.15, (100.0, 10000.0))
463                .with_description("Collection"),
464            ExpectedFlow::new("5100", "2100", 0.08, (5000.0, 50000.0))
465                .with_description("Hosting invoice"),
466            ExpectedFlow::new("2100", "1100", 0.10, (1000.0, 50000.0))
467                .with_description("Vendor payment"),
468            ExpectedFlow::new("6510", "1100", 0.05, (1000.0, 20000.0))
469                .with_description("Marketing spend"),
470        ];
471
472        template
473    }
474
475    /// Get template for a company archetype.
476    pub fn for_archetype(archetype: &CompanyArchetype) -> Self {
477        match archetype.industry() {
478            Industry::Retail => Self::retail_standard(),
479            Industry::SaaS => Self::saas_standard(),
480            Industry::Manufacturing => Self::gaap_minimal(), // TODO: manufacturing template
481            Industry::ProfessionalServices => Self::gaap_minimal(), // TODO
482            Industry::FinancialServices => Self::gaap_minimal(), // TODO
483            _ => Self::gaap_minimal(),
484        }
485    }
486
487    /// Get account count.
488    pub fn account_count(&self) -> usize {
489        self.accounts.len()
490    }
491
492    /// Find account definition by code.
493    pub fn find_by_code(&self, code: &str) -> Option<&AccountDefinition> {
494        self.accounts.iter().find(|a| a.code == code)
495    }
496
497    /// Get accounts by type.
498    pub fn accounts_by_type(&self, account_type: AccountType) -> Vec<&AccountDefinition> {
499        self.accounts
500            .iter()
501            .filter(|a| a.account_type == account_type)
502            .collect()
503    }
504}
505
506#[cfg(test)]
507mod tests {
508    use super::*;
509
510    #[test]
511    fn test_gaap_minimal() {
512        let template = ChartOfAccountsTemplate::gaap_minimal();
513        assert!(template.account_count() > 20);
514
515        // Should have basic account types
516        assert!(!template.accounts_by_type(AccountType::Asset).is_empty());
517        assert!(!template.accounts_by_type(AccountType::Liability).is_empty());
518        assert!(!template.accounts_by_type(AccountType::Revenue).is_empty());
519        assert!(!template.accounts_by_type(AccountType::Expense).is_empty());
520    }
521
522    #[test]
523    fn test_retail_template() {
524        let template = ChartOfAccountsTemplate::retail_standard();
525        assert!(template.account_count() > 30);
526        assert_eq!(template.industry, Industry::Retail);
527
528        // Should have retail-specific accounts
529        assert!(template.find_by_code("1110").is_some()); // Cash Registers
530        assert!(template.find_by_code("1120").is_some()); // Undeposited Funds
531    }
532
533    #[test]
534    fn test_saas_template() {
535        let template = ChartOfAccountsTemplate::saas_standard();
536        assert_eq!(template.industry, Industry::SaaS);
537
538        // Should have SaaS-specific accounts
539        assert!(template.find_by_code("2310").is_some()); // Deferred Revenue
540        assert!(template.find_by_code("4110").is_some()); // Subscription Revenue
541    }
542}